I have an array of mongoDB ids.
const pId = ['62b3968ad7cc2315f39450f3', '62b37f9b99b66e7287de2d44']
I used forEach to seperate the IDs like :
pId.forEach((item)=>{
console.log(item)
})
but I have a database(products) from where I want to fetch data from. So I tried
const [product, setProduct] = useState([{}]);
useEffect(() => {
pId?.forEach((item) => {
const getProduct = async () => {
try {
const res = await userRequest.get("/products/find/" item)
setProduct(res.data)
} catch (err) {
console.log(err)
}
}
getProduct()
})
}, [pId])
I used useState[{}]
because I want to collect the data in an array of objects.
CodePudding user response:
I used useState[{}] because I want to collect the data in an array of objects.
Your code isn't collecting objects into an array. It's setting each result of the query as the single state item (overwriting previous ones).
If you want to get all of them as an array, build an array; one way to do that is map
with the map
callback providing a promise of each element, then use Promise.all
to wait for all of those promises to settle:
// The usual advice is to use plurals for arrays, not singulars ("products", not "product")
const [products, setProducts] = useState([]); // Start with an empty array
useEffect(() => {
if (pId) {
Promise.all(pId.map((item) => userRequest.get("/products/find/" item)))
.then((products) => setProducts(products))
.catch((error) => console.log(error));
}
}, [pId]);
Note that if pId
changes while one or more userRequest.get
calls are still outstanding, you'll get into a race condition. If userRequest.get
provides a way to cancel in-flight calls (like fetch
does), you'll want to use that to cancel the in-flight calls using a cleanup callback in the useEffect
. For example, if userRequest.get
accepted an AbortSignal
instance (like the built-in fetch
does), it would look like this:
const [products, setProducts] = useState([]);
useEffect(() => {
const controller = new AbortController();
const { signal } = controller;
if (pId) {
Promise.all(pId.map((item) => userRequest.get("/products/find/" item, { signal })))
.then((products) => setProducts(products))
.catch((error) => {
if (!signal.aborted) {
console.log(error);
}
});
}
return () => {
controller.abort();
};
}, [pId]);
Again, that's conceptual; userRequest.get
may not accept an AbortSignal
, or may accept it differently; the goal there is to show how to cancel a previous request using a useEffect
cleanup callback.
CodePudding user response:
You can map through the ids and create a promise for each, then use Promise.all()
and at last set the products state:
import React, {
useState,
useEffect
} from 'react'
const Example = () => {
const [products, setProducts] = useState([]);
const ids = ['62b3968ad7cc2315f39450f3', '62b37f9b99b66e7287de2d44']
useEffect(() => {
if(ids) Promise.all(ids.map(id => userRequest.get("/products/find/" id).then(r => r.data))).then(results => setProducts(results))
}, [ids])
}
I renamed some of the variables for clarity to the future visitors. (pId
to ids
and product
to products
).
CodePudding user response:
You're overwriting your product
array with an individual result from each request. A simple solution would be to append to the array instead:
setProduct(product => [...product, res.data]); // take the old array and append the new item
As T.J. Crowder rightly suggested in the comments, you would keep appending to the initial product
state when using the simple setter, so you need to use callback form which receives the current state as a parameter and returns the update.
I suggest you rename that particular state to products
/setProducts
to make clear it's an array.
Not directly related to the question, but bear in mind that firing a huge number of individual requests may cause performance degradation on the client and backend; there are plenty of options to deal with that, so I am not going into more detail here.
CodePudding user response:
yes, it's possible. just change your code a bit:
const [product, setProduct] = useState([]); // empty array is enough for initialzing
useEffect(() => {
async function doSomethingAsync() {
if(!pId) return;
let newArray = [];
for(let item of pId) {
try {
const res = await userRequest.get("/products/find/" item)
newArray.push(res.data); // push data to temporary array
} catch (err) {
console.log(err)
}
}
// set new state once
setProduct(newArray);
}
doSomethingAsync();
}, [pId])