access queryKey in react-qyery - react-query

I have a separate function where I am returning useQueries. Inside the function, I have a query key and query function for each query. I want to have access to the query key outside of the function. I couldn't find any documentation/resource online regarding this one. Please help!
In the example code provided below, I will need access to the queryKey at the place where I am calling the result.
const result = () => {
return useQueries(
users.map(user => ({
queryKey :[`INeedThisValue`]
queryFn:()=> client.getUserData().toPromise()
}

I'm afraid there is no such thing built-in, but you can implement it by yourself, something like that:
const result = () => {
return useQueries(
users.map((user) => ({
queryKey: [`INeedThisValue`],
queryFn: () =>
client
.getUserData()
.toPromise()
.then((data) => {
return {
data,
queryKey: [`INeedThisValue`]
};
})
}))
);
};
And then get it like data[0].queryKey from your query result.

Related

react-query: accessing the query with matching condition (by 1st index of queryKey array)

I've queries like:
useQuery(['myquery',{test:1}], fetchFn)
useQuery(['myquery',{test:2}], fetchFn)
useQuery(['myquery',{test:3}], fetchFn)
I would like to observe the data of all those queries with myquery without knowing the rest of the items of queryKey.
In documentation, as I understood it is possible to observe multiple queries but my matching condition seems not covered.
const observer = new QueriesObserver(queryClient, [
{ queryKey: ['post', 1], queryFn: fetchPost },
{ queryKey: ['post', 2], queryFn: fetchPost },
])
const unsubscribe = observer.subscribe(result => {
console.log(result)
unsubscribe()
})
I could only find similar usage for useIsFetching but it only gives a number of matching queries:
// How many queries matching the posts prefix are fetching?
const isFetchingPosts = useIsFetching(['posts'])
But I want to access the result of the queries, specifically the last updated one.
This is the best thing i can come up with using queryClient :
const Component = () => {
// match all queries with:
const keyPrefix = "courseSection_list";
// since it is loading state, it will trigger twice for each returning result
const matchingQueriesUpdated = useIsFetching([keyPrefix]);
const data = useMemo(() => {
const lastUpdatedMatchingQuery = queryClient.queryCache.queries
.filter((q) => q.queryKey[0] === keyPrefix)
.sort((a, b) => b.state.dataUpdatedAt - a.state.dataUpdatedAt)[0] // sorting puts the last updated one to the 1st index;
return lastUpdatedMatchingQuery.state.data;
}, [matchingQueriesUpdated]);
return <div> bla bla </div>
}
Extra render can be prevented by catching dataUpdatedAt value at 0 for loading state. But i rather keep my code more simple for now.

Mongoose: Defining 404 status for not finding a document doesnt work

I,m learning MongoDB and mongoose and now I have a problem in defining a 404 status for my route handler. Here is the code:
app.get('/users/:id', async (req, res) => {
const _id = req.params.id
try {
const user = await User.findById(_id)
if (!user) {
return res.status(404).send()
}
res.send(user)
} catch (error) {
res.status(500).send()
}
})
Now if I give it an id that doesn't exist, it doesn't give me 404 Not Found status. it only executes the catch block which is not what I want.
I would appreciate it if you tell me where I made mistake or tell me a way to get error handling for that.
Thanks
The problem
As you can see in the log
CastError: Cast to ObjectId failed for value "6082d50a2c89db3164" at path "_id" for model "User"
It means : the value you provide to findById function ("6082d50a2c89db3164") is not a valid ObjectId.Then the catch block is executed.
Suggestion
1. Validate the parameter before query in database
I understand that you're trying to provide some id that doesn't exist in the database to test. But IMHO, there a difference between 2 cases :
you provide a valid id, and this id cannot be found in the database. It should return 404 in this case
you provide an invalid id in the request, it could be a string like "6082d50a2c89db3164", or even "#Q*&$(##*" or anything we could imagine. For this case, it could be better if we validate the input (req.params._id) to ensure that the format is valid. The code will be something like this:
app.get('/users/:id', async (req, res) => {
const _id = req.params.id;
// validate params
if(!isValidateObjectId(_id)) { // the function we need to write
res.status(200).send("Invalid params"); // you can define your status and message
return;
}
// good params, get user from database
try {
const user = await User.findById(_id)
if (!user) {
return res.status(404).send()
}
res.send(user)
} catch (error) {
res.status(500).send()
}
})
2. Use findOne() method instead of findById
If you want a simpler solution, don't use findById because the function expects a valid ObjectId. We can use findOne() method :
app.get('/users/:id', async (req, res) => {
const _id = req.params.id
try {
const user = await User.findOne({_id : _id})
if (!user) {
return res.status(404).send()
}
res.send(user)
} catch (error) {
res.status(500).send()
}
})
(IMHO, the first solution is better though..)
Some helpful link :
https://docs.mongodb.com/manual/reference/method/ObjectId/
Can I determine if a string is a MongoDB ObjectID?
https://mongoosejs.com/docs/api.html#model_Model.findOne

Cloud Functions: Transaction with FieldValue.increment() not running atomically

I have a Cloud Functions transaction that uses FieldValue.increment() to update a nested map, but it isn't running atomically, thus the value updates aren't accurate (running the transaction in quick succession results in an incorrect count).
The function is fired via:
export const updateCategoryAndSendMessage= functions.firestore.document('events/{any}').onUpdate((event, context) => {
which include the following transaction:
db.runTransaction(tx => {
const categoryCounterRef = db.collection("data").doc("categoryCount")
const intToIncrement = event.after.data().published == true ? 1 : -1;
const location = event.after.data().location;
await tx.get(categoryCounterRef).then(doc => {
for (const key in event.after.data().category) {
event.after.data().category[key].forEach(async (subCategory) => {
const map = { [key]: { [subCategory]: FieldValue.increment(intToIncrement) } };
await tx.set(categoryCounterRef, { [location]: map }, { merge: true })
})
}
},
).then(result => {
console.info('Transaction success!')
})
.catch(err => {
console.error('Transaction failure:', err)
})
}).catch((error) => console.log(error));
Example:
Value of field to increment: 0
Tap on button that performs the function multiple times in quick succession (to switch between true and false for "Published")
Expected value: 0 or 1 (depending on whether reference document value is true or false)
Actual value: -3, 5, -2 etc.
As far as I'm aware, transactions should be performed "first come, first served" to avoid inaccurate data. It seems like the function isn't "queuing up" correctly - for lack of a better word.
I'm a bit stumped, would greatly appreciate any guidance with this.
Oh goodness, I was missing return...
return db.runTransaction(tx => {

Optional chaining of methods like sort, limit, skip

I'm passing the query params and there could be any combination of sort, limit or skip for the Mongoose Query.
What I've thought of is to create mutliple mongoose queries based on what params have been passed. So if only sort is passed then the resulting query will have Document.find({}).sort({}) and incase only limit is passed then query would be Document.find({}).limit({})
Should I write something like this -
router.get('/', (req, res) => {
if(req.query.sortByPrice) {
Property.find({})
.sort({ price: req.query.sortByPrice === 'asc' ? 1 : -1 })
.populate('user_id', 'name')
.exec((err, properties) => {
if (err)
return res
.status(404)
.json({ error: "Can't get user details!" });
res.status(200).json(properties);
});
}
if(req.query.limit) {
Property.find({})
.limit(req.query.limit)
.populate('user_id', 'name')
.exec((err, properties) => {
if (err)
return res
.status(404)
.json({ error: "Can't get user details!" });
res.status(200).json(properties);
});
}
});
You can create a variable options from your request body and pass it as the third argument to the .find() query, no need to write redundant code with if-else block.
Secnd argument to .find() query is projection, so don't forget to pass an empty object there.
Try this :
let options={};
if(req.query.sortByPrice){
options.sort = {
price: req.query.sortByPrice === 'asc' ? 1 : -1
}
}
if(req.query.limit){
options.limit = req.query.limit
}
Property.find({},{},options)
.populate('user_id', 'name')
.exec((err, properties) => {
if (err)
return res
.status(404)
.json({ error: "Can't get user details!" });
res.status(200).json(properties);
return;
});
Note: Dont forget to return after res.status().json(), otherwise you might get error cant set headers after they are sent . if you try to send response again.

Uspert multiple documents with MongoDB/Mongoose

Say I have a list of models:
const documents = [{}, {}, {}];
And I want to insert these into the DB, or update them all, but only if a condition is met:
Model.update({isSubscribed: {$ne: false}}, documents, {upsert:true},(err, result) => {
});
The above signature is surely wrong - what I want to do is insert/update the documents, where the condition is met.
There is this Bulk API:
https://docs.mongodb.com/manual/reference/method/Bulk.find.upsert/
but I can't tell if it will work when inserting multiple documents.
Imagine this scenario: We have a list of employees and a form of some sorts to give them all a penalty, at once, not one by one :)
On the backend side, you would have your eg addBulk function. Something like this:
Penalty controller
module.exports = {
addBulk: (req, res) => {
const body = req.body;
for (const item of body) {
Penalty.create(item).exec((err, response) => {
if (err) {
res.serverError(err);
return;
}
});
res.ok('Penalties added successfully');
}
}
Then you'll probably have an API on your frontend that directs to that route and specific function (endpoint):
penaltyApi
import axios from 'axios';
import {baseApiUrl} from '../config';
const penaltyApi = baseApiUrl + 'penalty'
class PenaltyApi {
static addBulk(penalties) {
return axios({
method: 'post',
url: penaltyApi + '/addBulk',
data: penalties
})
}
}
export default PenaltyApi;
...and now let's make a form and some helper functions. I'll be using React for demonstration, but it's all JS by the end of the day, right :)
// Lets first add penalties to our local state:
addPenalty = (event) => {
event.preventDefault();
let penalty = {
amount: this.state.penaltyForm.amount,
unit: this.state.penaltyForm.unit,
date: new Date(),
description: this.state.penaltyForm.description,
employee: this.state.penaltyForm.employee.value
};
this.setState(prevState => ({
penalties: [...prevState.penalties, penalty]
}));
}
Here we are mapping over our formData and returning the value and passing it to our saveBulkEmployees() function
save = () => {
let penaltiesData = Object.assign([], this.state.penalties);
penaltiesData.map(penal => {
penal.employeeId = penal.employee.id;
delete penal.employee;
return penaltiesData;
});
this.saveBulkEmployees(penaltiesData);
}
...and finally, let's save all of them at once to our database using the Bulk API
saveBulkEmployees = (data) => {
PenaltyApi.addBulk(data).then(response => {
this.success();
console.log(response.config.data)
this.resetFormAndPenaltiesList()
}).catch(error => {
console.log('error while adding multiple penalties', error);
throw(error);
})
}
So, the short answer is YES, you can absolutely do that. The longer answer is above :) I hope this was helpful to you. If any questions, please let me know, I'll try to answer them as soon as I can.