Deriving an array with .map() inside a component body creates a new array reference on every render, even if the contents are identical. If that array is in a useEffect dependency list, it triggers an infinite loop.
// BAD: entries is a new array every render, useEffect fires forever
const entries = entriesWithContent.map(e => e.entry)
useEffect(() => {
fetchReactions(entries)
}, [entries]) // new reference every render = infinite loop
// GOOD: useMemo stabilizes the reference
const entries = useMemo(
() => entriesWithContent.map(e => e.entry),
[entriesWithContent]
)
useEffect(() => {
fetchReactions(entries)
}, [entries]) // only fires when entriesWithContent actually changes
This applies to any derived value in a dependency array: .filter(), .reduce(), Object.keys(), spread into new object, etc. If it creates a new reference, wrap it in useMemo.
React's comparison is referential (Object.is), not structural. Two arrays with identical contents are still different objects.
React useMemo Prevents Infinite useEffect Loops
Deriving an array with
.map()inside a component body creates a new array reference on every render, even if the contents are identical. If that array is in auseEffectdependency list, it triggers an infinite loop.This applies to any derived value in a dependency array:
.filter(),.reduce(),Object.keys(), spread into new object, etc. If it creates a new reference, wrap it inuseMemo.React's comparison is referential (
Object.is), not structural. Two arrays with identical contents are still different objects.