React Query and dealing with same behavior of queries in multiple files - react-query

This is more of an architectural question. When I was using redux-sages, I could just have a "worker saga" that made my api calls (yields) and when my 3 pages that needed this bahavior, I would just apply a case for it, and it would run on those pages.
But I moved away from all that and am now using react-query. But I am curious how people are dealing with, lets say, I have 3 custom useQuery calls... how are you consolidating them to include them INTO a couple pages, so you don't have to dupe that work?
So, lets say right now I have in fileA:
const { status, data: { plans} } = useCustomQuery1();
const { data: { client } } = useCustomQuery2({});
const {
data: { prices } = {} } = useCustomQuery3({
enabled: client.id,
id: client.id,
});
/* refetch: call later */
const {refetch } = useCustomQuery3({
id: client.id,
config: {
enabled: false
}
});
so, in my "fileA", I can get at refetch/prrices/plans/status etc... BUT, if I extract this out, so I use another "queries.js" file and name ala....
// queries.js
const useMySharedQueries1 = () => {
const { status, data: { plans} } = useCustomQuery1();
const { data: { client } } = useCustomQuery2({});
const {
data: { prices } = {} } = useCustomQuery3({
enabled: client.id,
id: client.id,
});
/* refetch: call later */
const {refetch } = useCustomQuery3({
id: client.id,
config: {
enabled: false
}
});
return {
refetch, prices, plans
}
}
I feel like there is a better way, more robust and succinct way to achieve this? Would I use "useQueries", but then does that deal with "refetch" queires or ones that need "enabled" etc.. and what about returning "all that data" so the consuming pages can use it?

Related

Different Read/Write types for FirestoreDataConverter

Is there a way to use different types for reading and writing data using the FirebaseDataConverter?
The typing of FirebaseDataConverter<T> suggest that there should only be a single type T, which is both what you would get back when querying and what you should provide when writing.
But in the scenario outlined below, I have two types, InsertComment which is what I should provide when creating a new comment, and Comment, which is an enriched object that has the user's current name and the firebase path of the object added to it.
But there is no way to express that I have these two types. Am I missing something?
type Comment = { userId: string, userName: string, comment: string, _firebasePath: string }
type InsertComment = { userId: string, comment: string }
function lookupName(_id: string) { return 'Steve' }
const commentConverter: FirestoreDataConverter<Comment> = {
fromFirestore(snapshot, options) {
const { userId, comment } = snapshot.data(options)
return {
userId,
comment,
name: lookupName(userId),
_firebasePath: snapshot.ref.path,
} as any as Comment
},
// Here I wish I could write the below, but it gives me a type error
// toFirestore(modelObject: InsertComment) {
toFirestore(modelObject) {
return modelObject
},
}
const commentCollection = collection(getFirestore(), 'Comments').withConverter(commentConverter)
// This works great and is typesafe
getDocs(commentCollection).then(snaps => {
snaps.docs.forEach(snap => {
const { comment, userName, _firebasePath } = snap.data()
console.info(`${userName} said "${comment}" (path: ${_firebasePath})`)
})
})
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// This gives me the type-error: that fields "userName, _firebasePath" are missing
addDoc(commentCollection, { comment: 'Hello World', userId: '123' })
I found a workaround, but I don't think this ought to be the way it should be done. It feels hacky.
Basically, I make two DataConverters, one for reading and one for writing.
I make the one for reading the default one, and when I need to write, I overwrite the read-converter with the write-converter.
function createReadFirestoreConverter<T>(validator: Validator<T>): FirestoreDataConverter<T> {
return {
fromFirestore(snapshot, options) {
return validator({ ...snapshot.data(options), _id: snapshot.id, _path: snapshot.ref.path })
},
toFirestore() {
throw new Error('Firestore converter not configured for writing')
},
}
}
function createWriteFirestoreConverter<T>(validator: Validator<T>) {
return {
fromFirestore() {
throw new Error('Firestore converter not configured for reading')
},
toFirestore(modelObject: any) {
return validator(modelObject)
},
} as FirestoreDataConverter<any>
}
const installedComponentConverterRead = createReadFirestoreConverter(installedComponentValidator)
const installedComponentConverterWrite = createWriteFirestoreConverter(newInstalledComponentValidator)
const readCollection = collection(getFirestore(), `MachineCards/${machineCard._id}/Components`).withConverter(installedComponentConverterRead)
// If I need to write
const docRef = doc(readCollection, 'newDocId').withConverter(installedComponentConverterWrite)

Using the $inc function across the MongoDB documents

I am currently working on a moderation system for my Discord bot and came across an unexpected issue. I've been using the $inc function to increase the values for a single document, though I have sadly not achieved to use the $inc function across multiple different documents, meaning I would like to increase ($inc) the value of the new document according to the numbers of the previous document.
Example: Cases
Current code:
async run(client, message, args, Discord) {
const targetMention = message.mentions.users.first()
const userid = args[0]
const targetId = client.users.cache.find(user => user.id === userid)
const username = targetMention.tag
if(targetMention){
args.shift()
const userId = targetMention.id
const WarnedBy = message.author.tag
const reason = args.join(' ')
if(!reason) {
message.delete()
message.reply('You must state the reason behind the warning you are attempting to apply.').then(message => {
message.delete({ timeout: 6000})
});
return;
}
const warningApplied = new Discord.MessageEmbed()
.setColor('#ffd200')
.setDescription(`A warning has been applied to ${targetMention.tag} :shield:`)
let reply = await message.reply(warningApplied)
let replyID = reply.id
message.reply(replyID)
const warning = {
UserId: userId,
WarnedBy: WarnedBy,
Timestamp: new Date().getTime(),
Reason: reason,
}
await database().then(async database => {
try{
await warnsSchema.findOneAndUpdate({
Username: username,
MessageID: replyID
}, {
$inc: {
Case: 1
},
WarnedBy: WarnedBy,
$push: {
warning: warning
}
}, {
upsert: true
})
} finally {
database.connection.close()
}
})
}
if(targetId){
args.shift()
const userId = message.member.id
const WarnedBy = message.author.tag
const reason = args.join(' ')
if(!reason) {
message.delete()
message.reply('You must state the reason behind the warning you are attempting to apply.').then(message => {
message.delete({ timeout: 6000})
});
return;
}
const warning = {
userId: userId,
WarnedBy: WarnedBy,
timestamp: new Date().getTime(),
reason: reason
}
await database().then(async database => {
try{
await warnsSchema.findOneAndUpdate({
userId,
}, {
$inc: {
Case: 1
},
WarnedBy: WarnedBy,
$push: {
warning: warning
}
}, {
upsert: true
})
} finally {
database.connection.close()
}
const warningApplied = new Discord.MessageEmbed()
.setColor('#ffd200')
.setDescription(`A warning has been applied to ${targetId.tag} :shield:`)
message.reply(warningApplied)
message.delete();
})
}
}
Schema attached to the Code:
const warnsSchema = database.Schema({
Username: {
type: String,
required: true
},
MessageID: {
type: String,
required: true
},
Case: {
type: Number,
required: true
},
warning: {
type: [Object],
required: true
}
})
module.exports = database.model('punishments', warnsSchema)
Answer to my own question. For all of those who are attempting to do exactly the same as me, there is an easier way to get this to properly work. The $inc (increase) function will not work as the main property of a document. An easier way to implement this into your database would be by creating a .json file within your Discord bot files and adding a line such as the following:
{
"Number": 0
}
After that, you'd want to "npm i fs" in order to read directories in live time.
You can proceed to add a function to either increase or decrease the value of the "Number".
You must make sure to import the variable to your current coding document by typing:
const {Number} = require('./config.json')
config.json can be named in any way, it just serves as an example.
Now you'd be able to console.log(Number) in order to make sure the number is what you expected it to be, as well as you can now increase it by typing Number+=[amount]
Hope it was helpful.

Using VUEX necessary with NODE.js REST Backend

I´m not very experienced with Frontend/Backend Architecture, but i created a simple REST Backend with NODE.js and want to build up a Frontend based on Vue.js and Framework7.
So do you recommend using VUEX there? Or how do you deal with the sessions or the different requests you sending to the Backend?
Thanks a lot!
You don't have to use Vuex, but I'd suggest using Vuex. Here's an example using Vuex and rest api.
In store/actions.js
import {
fetchSomething,
} from '../api/index.js';
export const actions = {
getSomething({ commit }) {
fetchSomething().then((something) => {
commit('UPATED_SOMETHING', something);
});
},
}
In api/index.js
export const fetchSomething = () => {
const url = 'Some endpoint';
return new Promise((resolve) => {
axios.get(url).then((res) => {
const data = res.data;
resolve(data);
}).catch((err) => {
console.log(err);
})
})
}
In store/mutations.js
export const mutations = {
UPATED_SOMETHING(state, data) {
state.something = data;
},
}
In store/index.js
import { getters } from './getters'
import { actions } from './actions'
import { mutations } from './mutations'
// initial state
const state = {
something: null,
}
export default {
state,
getters,
actions,
mutations,
}
In store/getters.js
export const getters = {
getSomething: state => {
return state.something;
},
}

How can I increment a counter variable in LoopBack 4 with a MongoDB datasource?

I'm trying to convert my Nodejs Express app to Loopback 4 and I can't figure out how to increment a counter. In my Angular 9 app when a user clicks an icon a counter is incremented. This works perfectly in Express
In Express
const updateIconCount = async function (dataset, collection = 'icons') {
let query = { _id: new ObjectId(dataset.id), userId: dataset.userId };
return await mongoController.update(
collection,
query,
{ $inc: { counter: 1 } },
function (err, res) {
logAccess(res, 'debug', true, 'function update updateIconLink');
if (err) {
return false;
} else {
return true;
}
}
);
};
I tried to first get the value of counter and then increment but every time I save VS Code reformats the code in an an unusual way. In this snippet I commented out the line of code that causes this reformatting. I can set the counter value, e.g. 100.
In Loopback 4
#patch('/icons/count/{id}', {
responses: {
'204': {
description: 'Icons PATCH success',
},
},
})
async incrementCountById(
#param.path.string('id') id: string,
#requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Icons, {partial: true}),
},
},
})
icons: Icons,
): Promise<void> {
// let targetIcon = this.findById(id).then(icon => {return icon});
icons.counter = 100;
console.log(icons.counter);
await this.iconsRepository.updateById(id, icons);
}
How do I implement { $inc: { counter: 1 } } in Loopback 4?
Added to aid solution
My mongo.datasource.ts
import {inject, lifeCycleObserver, LifeCycleObserver} from '#loopback/core';
import {juggler} from '#loopback/repository';
const config = {
name: 'mongo',
connector: 'mongodb',
url: '',
host: '192.168.253.53',
port: 32813,
user: '',
password: '',
database: 'firstgame',
useNewUrlParser: true,
allowExtendedOperators: true,
};
// Observe application's life cycle to disconnect the datasource when
// application is stopped. This allows the application to be shut down
// gracefully. The `stop()` method is inherited from `juggler.DataSource`.
// Learn more at https://loopback.io/doc/en/lb4/Life-cycle.html
#lifeCycleObserver('datasource')
export class MongoDataSource extends juggler.DataSource
implements LifeCycleObserver {
static dataSourceName = 'mongo';
static readonly defaultConfig = config;
constructor(
#inject('datasources.config.mongo', {optional: true})
dsConfig: object = config,
) {
super(dsConfig);
}
}
Amended endpoint
#patch('/icons/count/{id}', {
responses: {
'204': {
description: 'Icons PATCH success',
},
},
})
async incrementCountById(
#param.path.string('id') id: string,
#requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Icons, {partial: true}),
},
},
})
icons: Icons,
): Promise<void> {
console.log(id);
// #ts-ignore
await this.iconsRepository.updateById(id, {$inc: {counter: 1}});//this line fails
// icons.counter = 101; //these lines will set the icon counter to 101 so I know it is connecting to the mongodb
// await this.iconsRepository.updateById(id, icons);
}
You can use the mongo update-operators.
Basically, you just have to set allowExtendedOperators=true at your MongoDB datasource definition (guide). After that, you can directly use these operators.
Usage example:
// increment icon.counter by 3
await this.iconsRepository.updateById(id, {$inc: {counter: 3}} as Partial<Counter>);
Currently, these operators are missing from the lb4 types so you must cheat typescript to accept them. It's ugly but that's the only solution I could find right now.
You can follow this issue to see what's going on with these operators.

How to avoid unexpected side effect in computed properties - VueJS

I am trying to prefill a form with data from a vuex store.In the code provided is the expected result, I need but I know that this is not the way to do it. I am fairly new to Vue/Vuex. The inputs use a v-model thats why i cant use :value="formInformation.parentGroup" to prefill.
data() {
return {
groupName: { text: '', state: null },
parentName: { text: '', state: null },
};
},
computed: {
formInformation() {
const groups = this.$store.getters.groups;
const activeForm = this.$store.getters.activeForm;
if (activeForm.groupIndex) {
const formInfo = groups[0][activeForm.groupIndex][activeForm.formIndex]
this.groupName.text = formInfo.name // Is there a way to not use this unexpected side effect ?
return formInfo;
} else {
return 'No Form Selected';
}
},
},
I searched for an answere for so long now that i just needed to ask it. Maybe i am just googling for something wrong, but maybe someone here can help me.
You are doing all right, just a little refactoring and separation is needed - separate all the logic to computed properties (you can also use mapGetters):
mounted() {
if (this.formInformation) {
this.$set(this.groupName.text, this.formInformation.name);
}
},
computed: {
groups() {
return this.$store.getters.groups;
},
activeForm() {
return this.$store.getters.activeForm;
},
formInformation() {
if (this.activeForm.groupIndex) {
return this.groups[0][this.activeForm.groupIndex][
this.activeForm.formIndex
];
}
}
}
You could either make groupName a computed property:
computed: {
groupName() {
let groupName = { text: '', state: null };
if (formInformation.name) {
return groupName.text = formInfo.name;
}
return groupName;
}
Or you could set a watcher on formInformation:
watch: {
formInformation: function (newFormInformation, oldFormInformation) {
this.groupName.text = formInfo.name;
}
},
Avoid mutating data property in computed.
Computed are meant to do some operation (eg. reduce, filter etc) on data properties & simply return the result.
Instead, you can try this:
computed: {
formInformation() {
const groups = this.$store.getters.groups;
const activeForm = this.$store.getters.activeForm;
if (activeForm.groupIndex) {
const formInfo = groups[0][activeForm.groupIndex][activeForm.formIndex]
// this.groupName.text = formInfo.name // <-- [1] simply, remove this
return formInfo;
} else {
return 'No Form Selected';
}
}
},
// [2] add this, so on change `formInformation` the handler will get called
watch: {
formInformation: {
handler (old_value, new_value) {
if (new_value !== 'No Form Selected') { // [3] to check if some form is selected
this.groupName.text = new_value.name // [4] update the data property with the form info
},
deep: true, // [5] in case your object is deeply nested
}
}
}