0

I am currently working on a simple todo crud app. I just cannot seem to get my data to be put out on the screen.

const Home = () => {

  const [todos,setTodos] = useState([]);

  const getTodos = async () =>{
    const querySnapshot = await getDocs(collection(db, "todos"));
    
    querySnapshot.forEach((doc) => {        
      setTodos([...todos,doc.data()])
      console.log(doc.data())
      console.log(todos)
    });  
  }

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

  return (    
    <div>Home</div>
  )
}

Now the console.log(doc.data()) shows every dataset alright, but the todos array either gets just the first value assigned or returns an empty array.

Any ideas?

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
jungs
  • 17
  • 6

2 Answers2

1

State setter operations are asynchronous, calling setTodos doesn't change what's in the array your todos variable points to. Instead, it schedules a new call to your component function in which you'll get the new data from useState. (More in this answer.) That's why both your console.log(todos) and your repeated calls to setTodos aren't working: Both are using oudated information from todos.

When setting state based on existing state (your current array), it's usually best to use the callback form of the state setter. So the minimal change would be:

// But keep reading!
querySnapshot.forEach((doc) => {        
    setTodos((todos) => [...todos, doc.data()]);
});

That would work, but rather than calling setTodos in a loop, you might want to do the loop and then call the setter once when you're done.

I don't use Firebase, but Nick Parsons found and posted this link saying you can get the docs as an array via a docs property. So you can call map on that array:

setTodos((todos) => [...todos, ...querySnapshot.docs.map((doc) => doc.data())]);
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    Glad to see that you're back :) - I don't use firebase either, so don't quote me on this, but from my limited usage of it in the past, I believe you have to use `querySnapshot.docs.map(...)`, as the QuerySnaphhot itself that you get back from `getDocs()` doesn't seem to have a `.map()` method, but it has a [`.docs`](https://firebase.google.com/docs/reference/js/firestore_.querysnapshot.md#querysnapshotdocs) property which represents an array. – Nick Parsons Aug 04 '23 at 09:27
  • 1
    Thanks @NickParsons! I couldn't find those docs, appreciate the link! (And the welcome. :-) ) – T.J. Crowder Aug 04 '23 at 09:30
  • Thank you so much! It worked like a charm and I was trying to get my head around it since yesterday! Just if you have time and patience to answer: Just so I get this right - if I call a method, which is setting the state of something, it is automatically rerendering the component? Makes sense so far, just to keep everything up to date. But I dont get exactly how using a callback is making a difference..thank you so much! – jungs Aug 04 '23 at 09:31
  • @jungs - Yes, setting state schedules a call to re-render the component. The difference with using a callback is covered in the React docs [here](https://react.dev/reference/react/useState#updating-state-based-on-the-previous-state). – T.J. Crowder Aug 04 '23 at 09:35
  • @T.J.Crowder - thank you so much for your time! To anybody else having this kind of issue: I set the state outside of the loop. Otherwise it gave me everything back twice, but now it is working finally... – jungs Aug 04 '23 at 10:05
0

instead of using setTodos([...todos,doc.data()])

try this

querySnapshot.forEach((doc) => {        
      setTodos((prev) => [...prev, doc.data()])
      console.log(doc.data())
      console.log(todos)
    });  
ETCasual
  • 106
  • 11
  • And remove the `console.log(todos)`, since it will only show outdated information, because [this](https://stackoverflow.com/questions/41446560/react-setstate-not-updating-state). – T.J. Crowder Aug 04 '23 at 09:00
  • @T.J.Crowder, true, i just didnt bother to change that – ETCasual Aug 04 '23 at 09:11