1

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?

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

1 Comment

beat me to it by 30 seconds :)
1

TIL about functional updates in useState

replacing the offending useEffect call with this fixed the issue

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.