I am trying to create a useEffect hook that will check my firebase database for an array of objects when the component loads or when there’s an update to the array. My problem is that it’s causing infinite rerendering if I include the usersDragons
array at the end of the useEffect
hook and I don’t understand why. If I leave it out, it’s no longer running at all.
I am new to React so this may be an obvious solution that I’m just not seeing.
useEffect(() => {
async function checkForUsersDragons(currentUser) {
try {
const usersDragonsFromDB = await getUsersDragonsFromDB(currentUser.uid);
const usersDragonsFromDBArray = Object.values(usersDragonsFromDB);
if (usersDragonsFromDBArray.length > 0) {
setUsersDragons(usersDragonsFromDBArray);
} else {
console.log("No dragons for this user");
}
} catch (error) {
console.error("Error fetching dragons:", error);
}
}
if (currentUser) {
checkForUsersDragons(currentUser);
}
}, [usersDragons]);
2
Answers
There are a few things that will cause a component to render again, for example: receiving new props, updated value from context, updated value from store and updating a state. In this
useEffect
hook, you are fetching data and setting it to the state calledusersDragons
. By doing that, React will queue a render because you changed the state. You have to pay attention to thedependency array
as well, you are basically telling to youruseEffect
hook to fetch data from your firebase and set to the state, but everytime that state changes, your code inside theuseEffect
will run again = infinite loop.Remove
usersDragons
from thedependency array
of youruseEffect
and addcurrentUser
. This way, your hook will fetch data when the component first render and ifcurrentUser
has changes.This is a case of Reactive values change unintentionally.
The following Object.values() always returns a new array. And the state setter updates the state every time with the new array. However, this process becomes infinite!. The reason for this issue has been detailed below step by step.
The problem in step by step
The point to take note that Object.values() returns a new array every time. It returns a new array even if the values from the database have not been changed.
It means the following : the comparison check below says the two arrays are different even if its values are the same. Since the array is new and different every time, useEffect will fire every time, and thus causes the infinite looping.
A Solution
The above comparison of the two arrays yielded the result that they are different even the data remains the same. This is because we compared the container – the array, which is new every time. However if we compare the values, then we shall get the desired result. The following sample code does the same. JSON.stringify() returns the string content of the given array.
Now coming to your question:
The following sample code is a full version of the solution. It is based on the points we have discussed so far.
Please note that the dependency has been changed from the state to a derived value. Still it is a reactive value since it depends on the state, therefore the same can be given in the dependency array. However, now we will get the desired result, the useEffect will fire only on changing the content of the array.
An additional option
It is understood from your question that the same state may be updated by the App as well. Although it is unusual, if that is a requirement, the same has been modelled in the sample code as well. The button will change the state to a random value. Please note that this is a change in the state triggered by the front-end application. And this change will also invoke useEffect and cause the database values to be retrieved again.
App.js
Citation:
Does some reactive value change unintentionally