Skip to content
Advertisement

React redux reducer as UseEffect dependency causes infinite loop

I am just diving deep into React. But the useEffect React hook still got me confused. I know that I can pass dependencies as an array to it to control rendering of the component. I have used props and local state to do and it works.

What’s got me still confused is when I pass redux reducer as a dependency. It causes an infinite loop of rendering the component.

users component

const usersComp = () => {
    const users = useSelector(state => state.users);

    useEffect(
        // Fetch users and update users state
        useDispatch().dispatch(getUsers)
    ,[users]) // <-- Causes an infinite loop!!

    if(users.length){
        return( users.map(user => <p>{user}</p>))
    }
}

getUsers Redux Thunk function

export async function getUsers(dispatch, getState) {
    fetch(endpoint)
        .then(response => response.json())
        .then(users => {
            dispatch({type: GET_USERS, payload: users})
        }).catch(err => console.error("Error: ", err));
}

users reducer

export default function usersReducer(state = [], action) {
    switch (action.type) {

        case GET_USERS : {
            return [...state, action.payload]
        }
    }
}

From what I understand, users start off as an empty array, and then gets filled with data from an API call. So useEffect should fire twice; when the component has just mounted and then when users state changes from the API call. So what’s causing the infinite loop?

Advertisement

Answer

Remove users from the useEffect dependency, because you want to fetch users when component mounts, not each time the users is changed.

useEffect(
    useDispatch().dispatch(getUsers)
,[]) // Now, it will fetch users ONLY ONCE when component is mounted

Explanation:

// Case 1
useEffect(() => {
  console.log("Mounted") // Printed only once when component is mounted
}, [])

// Case 2
useEffect(() => {
  console.log("users changed") // Printed each time when users is changed
}, [users])

So, if you do fetch in Case 2, it will change users which will retrigger the hook which will fetch the users again which changes the users and causes the hook to retrigger → This is an infinite loop.

Update:

Why is state.users getting changed (in this code), as detected by useEffect, even when values of state.users are “SAME” (Same values)?

Whenever GET_USERS action is dispatched, reducer returns new state ({ ...state, users: action.payload }). It does so even when value of action.payload holds the same value of users. This is why useEffect receives new users array. (They do shallow comparison.)

Note that, [1, 2,3] is not equal to [1, 2,3] i.e. [1, 2,3] === [1, 2,3] returns false.

If for some reason, you want to return the same redux state, do return state in the reducer. This is often what we do in the default case of switch of reducer.

Advertisement