Reputation: 497
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
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
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