I want to update useState
array values by calling a function that maps through an array (called from the database) and the useState
array will be updated for each item in the (database array) so I have tried the following approach:
const [snapshots, setSnapshots] = useState();
const [items, setItems] = useState([]);
// *** get from the database ***** //
useEffect(()=> {
db.collection("users").doc("4sfrRMB5ROMxXDvmVdwL").collection("basket")
.get()
.then((snapshot) => {
setSnapshots(snapshot.docs)
}
) ;
}, []);
// *** get from the database ***** //
// *** update items value ***** //
return <div className="cart__items__item">
{snapshots && snapshots.map((doc)=>(
setItems([...items, doc.data().id]),
console.log(items)
))
}
</div>
// *** update items value ***** //
but the following error appears:
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
I have tried to console.log
the result to see the check the issue and the Items
array was logged in the console continuously I have tried to include the code in a useEffect
but it did not work as well .
Never call a state setter at the top level of your component function. With function components, the key thing to remember is that when you change state, your function will get called again with the updated state. If your code has a state change at the top level of the function (as yours does in the question), every time the function runs, you change state, causing the function to run, causing another state change, and so on, and so on. In your code:
const initialArray = []; // *** 1
const [Items, setItems] = useState(initialArray) // *** 2
initialArray.push("pushed item")
setItems(initialArray) // *** 3
Items
when the component is createdInstead, you should be setting state only in response to some change or event, such as a click handler, or some other state changing, etc.
Also note that you must not directly modify an object (including an array) that you have in state. Your code doesn't technically do that (since there's a new initialArray
every time), but it looks like what you meant to do. To add to an array in state, you copy the array and add the new entry at the end.
An example of the above:
function Example() {
const [items, setItems] = useState([]);
const clickHandler = e => {
setItems([...items, e.currentTarget.value]);
};
return <div>
<div>
{items.map(item => <div key={item}>{item}</div>)}
</div>
<input type="button" value="A" onClick={clickHandler} />
<input type="button" value="B" onClick={clickHandler} />
<input type="button" value="C" onClick={clickHandler} />
</div>;
}
(Slightly odd UI just to keep the code example simple.)
Note that conventionally Items
would be called items
.
Re your update:
setItems
at the top level of the function, so it has the problem above. Instead, you do that work in the useEffect
querying the database.setItems
repeatedly during the map
operation.map
in the JSXE.g., something like this:
const [snapshots, setSnapshots] = useState();
const [items, setItems] = useState(); // *** If you're going to use `undefined`
// as the initial state of `snapshots`,
// you probably want to do the same with
// `items`
useEffect(()=> {
let cancelled = false;
db.collection("users").doc("4sfrRMB5ROMxXDvmVdwL").collection("basket")
.get()
.then((snapshot) => {
// *** Don't try to set state if we've been unmounted in the meantime
if (!cancelled) {
setSnapshots(snapshot.docs);
// *** Create `items` **once** when you get the snapshots
setItems(snapshot.docs.map(doc => doc.data().id));
}
})
// *** You need to catch and handle rejections
.catch(error => {
// ...handle/report error...
});
return () => {
// *** The component has been unmounted. If you can proactively cancel
// the outstanding DB operation here, that would be best practice.
// This sets a flag so that it definitely doesn't try to update an
// unmounted component, either because A) You can't cancel the DB
// operation, and/or B) You can, but the cancellation occurred *just*
// at the wrong time to prevent the promise fulfillment callback from
// being queued. (E.g., you need it even if you can cancel.)
cancelled = true;
};
}, []);
// *** Use `items` here
return <div className="cart__items__item">
{items && items.map(id => <div>{id}</div>)/* *** Or whatever renders ID */}
</div>;
Note that that code assumes that doc.data().id
is a synchronous operation.