Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 314
David Okwii Avatar asked Oct 21 '25 22:10

David Okwii


1 Answers

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.

like image 83
Ajeet Shah Avatar answered Oct 23 '25 11:10

Ajeet Shah



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!