Home > Back-end >  How to make useEffect rerender after the data is changed?
How to make useEffect rerender after the data is changed?

Time:10-24

I want the useEffect to fetch and render rooms data. And rerender the data when the new room is being added to rooms

Code to fetch data and display data with useEffect and an empty dependency.

const [rooms, setRooms] = useState([]);

  const getRoomsData = async () => {
    const querySnapshot = await getDocs(collection(db, "rooms"));
    const data = querySnapshot.docs.map((doc) => ({
      id: doc.id,
      data: doc.data(),
    }));

    setRooms(data);
  };

  useEffect(() => {
    getRoomsData();
    console.log("rerendered");
  }, []);

With this code, after I added new room to rooms. I need to manually refresh the page to see the new data rendered. What should I do to make it rerender itself after new "room" is added to rooms?

Code to add room:

    const roomName = prompt("please enter name for chat room");

    if (roomName) {
      addDoc(collection(db, "rooms"), {
        name: roomName,
      });
    }
  };

I tried adding rooms to the useEffect dependency, which has the result I want (no need to refresh), but it is only because it is rendering infinitely, which is bad. What is the proper way of doing it?

CodePudding user response:

You are getting infinite re-renders because the function inside the useEffect is updating the state of rooms which is in the dependency array of the effect. This will always cause infinite re-renders.

To answer your question verbatim: "How do I make it rerender only when the data is changed?"

Since rooms and data are set to be the same, you can keep your useEffect how it is, but then create another useEffect to fire only when the component mounts to call getRoomsData().

const [rooms, setRooms] = useState([]);

  const getRoomsData = async () => {
    const querySnapshot = await getDocs(collection(db, "rooms"));
    const data = querySnapshot.docs.map((doc) => ({
      id: doc.id,
      data: doc.data(),
    }));

    setRooms(data);
  };

  useEffect(() = > {
    getRoomsData();
  }, [])

  useEffect(() => {
    console.log("rerendered");
  }, [rooms]);

I think the real crux of solving your issue is knowing when to call getRoomsData(), because depending on that, you will change the useEffect dependency array to fit that need.

CodePudding user response:

When getting data using useEffect, it expects a syncronous call to be made. Asking for data from Firestore will be asyncronous, so to get around this we can add a function wrapping the firestore call called getRoomsData like below:

const [rooms, setRooms] = useState([]);



  useEffect(() = > {
    const getRoomsData = async () => {
    const querySnapshot = await getDocs(collection(db, "rooms"));
    const data = querySnapshot.docs.map((doc) => ({
      id: doc.id,
      data: doc.data(),
    }));
 setRooms(data);
 
  };
    getRoomsData();
  }, [])
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

I can think of two approaches to solving this problem, without having to use useEffect, the first one being a workaround where you update the rooms state locally without having to fetch it from the server, i.e, you are basically constructing and appending an object to the existing array of rooms, this is possible only if you know the structure of the object, looking at your getRoomsData function, it seems that you are returning a data array with each item having the following object structure.

{
 id: <DOCUMENT_ID>,
 data: {
  name: <ROOM_NAME>
 }
}

So, when you add a room, you can do something like this:

   const roomName = prompt("please enter name for chat room");

    if (roomName) {
      addDoc(collection(db, "rooms"), {
        name: roomName,
      });
      let newRoom = {
        id: Math.random(), //random id
        data: {
          name: roomName
        }
      }
      setRooms([...rooms,newRoom])
  };

This way, you can also reduce the number of network calls, which is a performance bonus, but the only downside is that you've to be sure about the object structure so that the results are painted accordingly.
In case, you are not sure of the object structure, what you could do is instead invoke getRoomsData after you add your new room to the collection, like so:

   const roomName = prompt("please enter name for chat room");

    if (roomName) {
      addDoc(collection(db, "rooms"), {
        name: roomName,
      }).then(res=>{
          getRoomsData();
      })
  };

This is would ensure to fetch the latest data (incl. the recently added room) and render your component with all the results. In either of the methods you follow, you could use your useEffect only once when the component mounts with an empty dependency array.

 useEffect(() => {
    getRoomsData();
    //console.log("rerendered");
  }, []);

By doing this, you first basically display the data when the user visits this page for the first time/when the components mount for the first time. On subsequent additions, you can either append the newly added room as an object to the rooms array after adding it to the database or add it to the database and fetch all the results again, so that the latest addition is rendered on the screen.

  • Related