Search code examples
reactjsreduxreact-hooksinfinite-loopreact-state

React + Redux - Update local state on store change => infinity loop


I have an array that resides in store, let's call it users and I have a custom hook that retrieves it from store (via useSelector). Based on this array in the store, I have a local state in my component that I use as the display source of a table. I need to have it separately in local state since I filter the data based on a search functionality.

What I want to do is, every time when the data in the store changes, I want to update the local state as well to reflect those changes (but call the filtering function on top of it before hand).

This however results in an infinity loop since the useEffect + setState cause a redraw which changes the store variable again etc.

const users = useUsers() // uses redux store
const [displayUsers, setDisplayUsers] = useState(users) // local state

useEffect(() => {
   const filteredUsers = onSearch(users) // applies the filtering
   setDisplayUsers(filteredUsers) // updates the local state
}, [users])

return <Table source={displayUsers} ... />

Can anybody help?


Solution

  • Since displayUsers is derived from the props, you don't need to maintain it in the state. Calculate it on the fly whenever the props changes, and use memo if the component is rendered often for other reasons:

    const users = useUsers() // uses redux store
    
    const displayUsers = useMemo(() => onSearch(users), [onSearch, users])
    
    return <Table source={displayUsers} ... />
    

    If you really need to set the state when the store changes, make sure that the selector is memoized - ie if nothing changes you'll get the same array. So when re-rendering the component, the selector would return the same users array, and this will prevent the useEffect from being called again.

    You can call useSelector with the equalityFn (2nd param) to avoid returning a new value if nothing changed (see Equality Comparisons and Updates). This is the example from the docs:

    import { shallowEqual, useSelector } from 'react-redux'
    
    const selectedData = useSelector(selectorReturningObject, shallowEqual)