Home > Mobile >  react useState only stores the last document
react useState only stores the last document

Time:05-10

I am working on a react Netflix clone web app and I have stored some data on Firestore for making a favorite list. When I fetched data from Firestore and try to store in a state, some error occured.

There were about four documents in firebase but I only got the last one every time I try

I have included key to the map and try to give the state empty string as initial value, I tried spread operator but none of them worked

firestore is working well as i see those documents on console

const [movie, setMovie] = useState([])

useEffect(() => {
async function fetchData() {

  const q = query(collection(db, "movie"));
  const querySnapshot = await getDocs(q);
  querySnapshot.forEach((doc) => {

    setMovie([...movie, doc.data().Details])
    console.log(doc.data().Details)
    console.log(movie)
  });
}

fetchData()

CodePudding user response:

Your fetchData function closes over movie, which means it only sees the movie that existed when the function was created (you haven't shown enough to be sure, but I'm guessing you have an empty dependencies array on that useEffect, so that would be an always-empty movie). Doing setMovie([...movie, doc.data().Details]) just spreads that empty movie and then adds the one final document.

Instead, two things:

  1. Whenever updating state based on existing state, it's best to use the callback form of the setter so that you get the up-to-date version of the state you're updating.

  2. Collect the documents, then do just one setter call.

const [movies, setMovies] = useState([])

useEffect(() => {
    async function fetchData() {
        const q = query(collection(db, "movie"));
        const querySnapshot = await getDocs(q);
        setMovies(previousMovies => [
            ...previousMovies,
            ...querySnapshot.map((doc) => doc.data().Details)
        ]);
    }

    fetchData();
}, []); // <== I've assumed this

(Note I've made it a plural, since there's more than one movie.)

(I don't use MongoDB, but I've assumed from the forEach that querySnapshot is an array and so it has map. If not, it's easy enough to create the array, use forEach to push to it, and then do the setMovies call.)


But there's another thing: You should allow for the possibility your component is unmounted before the query completes. If your getDocs has a way to cancel its operation, you'll want to use that. For instance, if it accepted an AbortSignal, it might look like:

const [movies, setMovies] = useState([])

useEffect(() => {
    async function fetchData() {
        const controller = new AbortController();
        const { signal } = controller;
        const q = query(collection(db, "movie"));
        const querySnapshot = await getDocs(q, signal);
        if (!signal.aborted) {
            setMovies(previousMovies => [
                ...previousMovies,
                ...querySnapshot.map((doc) => doc.data().Details)
            ]);
        }
    }

    fetchData();
    return () => {
        controller.abort();
    };
}, []);

But if there's some other cancellation mechanism, naturally use that.

If there's no cancellation mechanism, you might use a flag so you don't try to use the result when it won't be used for anything, but that may be overkill. (React has backed off complaining about it when you do a state update after the component is unmounted, as it was usually a benign thing to do.)

CodePudding user response:

for a start, I suggest you to do something like the following:

const [movie, setMovie] = useState([])

useEffect(() => {
async function fetchData() {

  const q = query(collection(db, "movie"));
  const querySnapshot = await getDocs(q);
  setMovie(querySnapshot.flatMap(elt=>doc.data().Details));
}

fetchData()

CodePudding user response:

It's because you update your state on forEach loop, you start from first doc until the last one, in the end you set the last movie, that's why you have everytime the last doc on your state.

First of all don't update state too frequently, it creates app performance problems.

To fix it (based on your example):

     let movies=[]
     querySnapshot.forEach((doc) => {
        movies([...movie, doc.data().Details]);
 //or   movies.push(doc.data().Details);
      });
     setMovie(movies);

CodePudding user response:

Don't know if it will solve the problem, but when you set a state value base on the previous value (here setMovie([...movie, doc.data().Details])) you should use a callback function: https://en.reactjs.org/docs/hooks-reference.html#functional-updates

Like this:

setMovie(oldMovies => ([...oldMovies, doc.data().Details]))

If it still doesn't work try to create a new tab you're filling during the forEach, and at the end, set the state:

  const q = query(collection(db, "movie"));
  const querySnapshot = await getDocs(q);
  const newMovies = []
  querySnapshot.forEach((doc) => {
    newMovies.push(doc.data().Details)
  });
  setMovie([...movie, ...newMovies])
  • Related