jonesjones
jonesjones

Reputation: 497

How to avoid recursive dependencies in useEffect hook

I have a component which maintains a list of items that are fetched over time. The component fetches an item on load, and can also fetch new items as a result of user interaction. Whenever an item is fetched it should be added to the list of items. The following code goes into an infinite recursion, because each time I add a new item to items, the effect is called again with the new items list, and another item is added.

export default function UnintentionallyRecursive () {
  const [item, setItem] = useState()
  const [items, setItems] = useState([])

  const fetchItem = () => new Promise(resolve => resolve({ title: 'a new item' }))

  const updateItem = useCallback(async () => {
    const newItem = await fetchItem()
    setItem(newItem)
  }, [setItem])

  useEffect(() => {
    updateItem()
  }, [updateItem])

  // this is the part that causes the recursion
  useEffect(() => {
    setItems(items.concat([item]))
  }, [item, items, setItems])

  return null // really UI code which can also call updateItem
}

How can I achieve this using Hooks?

Upvotes: 1

Views: 3393

Answers (2)

Luuuud
Luuuud

Reputation: 4439

You can update a state using a function with its current state as argument. This way you don't need items as a dependency.

setItems((currentState) => currentState.concat(item));

// is the same as
setItems([items].concat(item));

Sidenote: You also don't need to add setItems to your dependency array, it's save to leave out.

Upvotes: 4

jonesjones
jonesjones

Reputation: 497

TIL about functional updates in useState

replacing the offending useEffect call with this fixed the issue

  useEffect(() => {
    setItems(items => items.concat([item]))
  }, [item, setItems])

Upvotes: 1

Related Questions