skip to Main Content

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


  1. 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 called usersDragons. By doing that, React will queue a render because you changed the state. You have to pay attention to the dependency array as well, you are basically telling to your useEffect hook to fetch data from your firebase and set to the state, but everytime that state changes, your code inside the useEffect will run again = infinite loop.

    Remove usersDragons from the dependency array of your useEffect and add currentUser. This way, your hook will fetch data when the component first render and if currentUser has changes.

    Login or Signup to reply.
  2. 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.

    useEffect(() => {
       ...
          const usersDragonsFromDBArray = Object.values(usersDragonsFromDB);
          setUsersDragons(usersDragonsFromDBArray);
       ...
    
    }, [usersDragons]);
    

    The problem in step by step

    //. Step 1 : Let us say the state usersDragons is initially an empty array [].
    //. Step 2 : When the app is mounted, useEffect will fire. 
                 // And will fetch values from the database. 
    //. Step 3 : The Object.values() call will return the values in a new array.
    //. Step 4 : The state setter will update the state with the new array.
    //. Issue  : Since the state has been changed above, the dependency of 
                 // useEffect has also been changed. This will cause the code 
                 // in useEffect to repeat. And the same process will repeat 
                 // forever.
    

    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.

    const arrayOne = Object.values(1,2,3); // [1,2,3] 
    const arrayTwo = Object.values(1,2,3); // [1,2,3]
    
    arrayOne === arrayTwo ? 'Same' : 'Different'. // Different
    

    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.

    const arrayOne = Object.values(1,2,3); // [1,2,3] 
    const arrayTwo = Object.values(1,2,3); // [1,2,3]
    
    JSON.stringify(arrayOne) === JSON.stringify(arrayTwo) ? 'Same' : 'Different'. // Same
    

    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

    import { useEffect, useState } from 'react';
    
    export default function App() {
      const [someArrayState, setArrayState] = useState([]);
    
      const someArrayStateString = JSON.stringify(someArrayState);
    
      useEffect(() => {
        async function fetchRandomData() {
          setTimeout(() => {
            setArrayState([1, 2]);
          }, 1000);
        }
        fetchRandomData();
      }, [someArrayStateString]);
    
      return (
        <>
          New array values : {someArrayStateString}
          <button onClick={() => setArrayState([Math.random()])}>
            Change the array by this frontend App
          </button>
        </>
      );
    }
    

    Citation:

    Does some reactive value change unintentionally

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search