Home > OS >  setState loop seems to only run on the last iteration
setState loop seems to only run on the last iteration

Time:01-21

So I have an app with friends and events, where each friends has specific events.
I just want to retrieve every single event from every single friend.
I have an 'event' state const [events, setEvents] = useState([]);
Then here is the problematic useEffect snippet:

useEffect(() => {
  getFriendsIds()
    .then(idsArray => {
      Promise.all(idsArray.map(id => {
        getUserEvents(id)
          .then(evs => {
            if (evs.length > 0) {
              for (let i=0; i<evs.length; i  ) {
                setEvents([...events, evs[i]]);
              }
            }
          })
      }))
    })
}, [])

This should store the aforementioned events in the 'events' state variable.
However, when I try to render them with {events.map(ev => renderItem(ev))}, I only get the very last event of the last friend in the loop (It is guaranteed that renderItem works fine).
I would love an explanation of how this works, and if possible an alternative algorithm that actually achieves what I aim to do.
Thanks

CodePudding user response:

Actually, this happens because your useEffect() doesn't have the events state in its dependency array.

useEffect(() => {
   ...
}, [events])

However event this solution will not work properly because the setState() function works asynchronously.

You have to use state updater function in this case:

useEffect(() => {
  ...
  setEvents((prevEvents) => [...prevEvents, evs[i]]);
  ...
}, []);

Read more about dependency array and state updater in the official documentation

CodePudding user response:

Try passing a function to setEvents and remove the for loop.

if (evs.length > 0) {
    setEvents((prev) => ([...prev, ...evs]));
}

CodePudding user response:

Move the setState out of the loop -

useEffect(async () => {
  const ids = await getFriendsIds()
  const events = await Promise.all(ids.map(getUserEvents)))
  setEvents(events.flat())      // replace
}, [])

If you want to append events to the existing events state, you can use a functional update -

useEffect(async () => {
  const ids = await getFriendsIds()
  const events = await Promise.all(ids.map(getUserEvents)))
  setEvents(r => [...r, ...events.flat()])    // append
}, [])

You can use .flat instead of checking for .length as it will remove empty arrays for you automatically -

const a = [["event1", "event2"], [], ["event3"], ["event4"], []]

console.log(a.flat())
// ["event1", "event2", "event3", "event4" ]

  •  Tags:  
  • Related