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.