This is happening because due to the fact that you're using a part of the Redux state (i.e. the
postswhich you extract from Redux store usinguseSelector), your component will automatically re-render when the state changes.However, each time you are mounting your component, you are also calling the
dispatchmethod in youruseEffectthat changes the Redux store and causes your component to re-render.Again, during the re-render, your
useEffectruns thedispatchfunction that causes a Redux store update which in turn causes yet another re-render and this is how you've gotten caught up in an infinite loop of re-renders and Redux store updatesA solution to this might be to only run
dispatchonly when thepayloadchanges by running theuseEffectexclusively whenpayloadchanges and not when component mountsYou can use the following code for the same :
useUpdateEffect - custom react hook that will only run when dependencies change
import { useEffect, useRef } from "react"
export default function useUpdateEffect(callback, dependencies) {
const firstRenderRef = useRef(true)
// Since ref persists value between renders (and itself doesn't trigger a render when value is changed), we can simply just set ref to a failing condition on our 1st render so that component only is re-rendered when dependencies change and not also "onMount"
useEffect(() => {
if (firstRenderRef.current) {
firstRenderRef.current = false
return
}
return callback()
}, dependencies)
}
Using the useUpdateEffect hook in our code :
const dispatch = useDispatch();
useUpdateEffect(() => {
dispatch(fetchPostsByTerm(payload));
}, [payload])
const posts = useSelector((state) => state.postsByTerm);
return (
<div>
<h1 className="post_heading">Posts</h1>
{posts ? posts.map((post) => <h1>{post.entityLable}</h1>) : <h1>no posts</h1>}
</div>
);
};
Answer from Ankit Sanghvi on Stack OverflowThis is happening because due to the fact that you're using a part of the Redux state (i.e. the
postswhich you extract from Redux store usinguseSelector), your component will automatically re-render when the state changes.However, each time you are mounting your component, you are also calling the
dispatchmethod in youruseEffectthat changes the Redux store and causes your component to re-render.Again, during the re-render, your
useEffectruns thedispatchfunction that causes a Redux store update which in turn causes yet another re-render and this is how you've gotten caught up in an infinite loop of re-renders and Redux store updatesA solution to this might be to only run
dispatchonly when thepayloadchanges by running theuseEffectexclusively whenpayloadchanges and not when component mountsYou can use the following code for the same :
useUpdateEffect - custom react hook that will only run when dependencies change
import { useEffect, useRef } from "react"
export default function useUpdateEffect(callback, dependencies) {
const firstRenderRef = useRef(true)
// Since ref persists value between renders (and itself doesn't trigger a render when value is changed), we can simply just set ref to a failing condition on our 1st render so that component only is re-rendered when dependencies change and not also "onMount"
useEffect(() => {
if (firstRenderRef.current) {
firstRenderRef.current = false
return
}
return callback()
}, dependencies)
}
Using the useUpdateEffect hook in our code :
const dispatch = useDispatch();
useUpdateEffect(() => {
dispatch(fetchPostsByTerm(payload));
}, [payload])
const posts = useSelector((state) => state.postsByTerm);
return (
<div>
<h1 className="post_heading">Posts</h1>
{posts ? posts.map((post) => <h1>{post.entityLable}</h1>) : <h1>no posts</h1>}
</div>
);
};
just write dispatch in dependency array like this [dispatch]
reactjs - Strange problems with useEffect and useSelector cause infinite loops - Stack Overflow
react native - Infinite loop with useEffect hook with useSelector - Stack Overflow
reactjs - useEffect goes in infinite loop when combined useDispatch, useSelector of Redux - Stack Overflow
getting infinite loop when using useSelector value inside useEffect
My understanding of
useEffectmonitoring should be that when the data is changed, he will re-execute the code inuseEffect.
Your understanding of the useEffect hook is correct, but you are not understanding that your useSelector hook is potentially returning a new array object reference each time the component renders. In other words, data is a new reference that triggers the useEffect hook to run. The useEffect hook enqueues a list state update. When React processes the list state update it triggers a component rerender. data is selected and computed from the Redux store. Repeat ad nauseam.
You can help break the cycle by using an equality function with the useSelector hook. See Equality Comparisons and Updates.
import { shallowEqual, useSelector } from 'react-redux';
...
const data = useSelector((state: DeviceList) => {
if (state.basicSetting !== null) {
return state.basicSetting.filter(
item =>
item.deviceName.includes('ac') &&
!item.deviceName.includes('bz'),
);
} else {
return null;
}
}, shallowEqual); // <-- shallow equality instead of strict equality
Another good solution is to create a memoized selector function from Reselect, and since you are using Redux-Toolkit, createSelector is re-exported from Reselect. (Reselect is maintained by the same Redux/Redux-Toolkit developers)
It allows you to write the same computed data value but it memoizes the result so that if the selected state going into the selector hasn't changed, then the selector returns the same previously computed value. In other words, it provides a stable returned data value that will only trigger the useEffect when state actually updates and the selector computes a new value reference.
However, it is a bit unclear why you are selecting state from the store and duplicating it locally into state. This is generally considered a React anti-pattern. You can compute the list value directly from the store.
Example:
const list = useSelector((state: DeviceList) => {
if (state.basicSetting === null) {
return null;
}
const data = state.basicSetting.filter(
item =>
item.deviceName.includes('ac') &&
!item.deviceName.includes('bz'),
) as DeviceDataOne[];
const list: DeviceDataOne[] = [];
while (data.length) {
list.push(data.splice(0, 4));
}
return list;
});
useEffect(() => {
console.log('list', list);
}, [list]);
My understanding of useEffect monitoring should be that when the data is changed, he will re-execute the code in useEffect.
That's correct, but data is being changed: the useSelector function is returning a new list each time your component is rendered, and in JavaScript, two lists aren't considered equal just because they currently contain the same elements.
The reason your effect is triggering an infinite loop is that it changes the list state every time, which triggers a new render, which changes data, which triggers the effect again.
The problem is that your selector always returns a new reference (because of the call to map). You could instead use createSelector which will memoize it and only return a new reference when something inside either allIds or byId changes:
const selectAllCategories = createSelector(
(state: AppState) => state.categories.allIds,
(state: AppState) => state.categories.byId,
(categoriesIds, categoriesById) => categoriesIds.map((id) => categoriesById[id.toString()])
);
But ideally you should avoid such selectors that go through the whole byId object, because it kind of negates the benefits of having a normalized state. You should rather have a parent component that only selects state.categories.allIds, and then passes the ids as props to child components, and every child component will select its own state.categories.byId[id]. This way if a category changes, only the corresponding child component will rerender, instead of having the parent and all of the children rerender.
In useEffect you update the state , wich cause render , render call useSelector wich every time return new array for useEffect , wich cause update the state. To fix you can remove categories from useEffect dependency array
Really sorry if this isn't the place to ask, I just thought if anyone would know it's someone on here 😊
I have a screen in a React-Native project which essentially just renders a loading icon whilst fetching data from the server, before then taking the user to the main screen. The first function getPrivateKey() will return the private key and store it using redux in the state, and the next function connectWithKey() will then use that key to connect.
The issue I'm facing is that when connectWithkey() runs, it's using the initial, empty value of the private key, not the updated value. Here's the code, and apologies if I'm being stupid it's been a long day :(
export default DataLoader = props => {
//private key - this should, in theory, update after getPrivateKey()
const privateKey = useSelector(({ main }) => main.privateKey);
const dispatch = useDispatch();
useEffect(() => {
const configure = async () => {
//this will update the private key
await getPrivateKey();
//this should use the new private key from useSelector, but instead is using
//the initialised empty object
await connectWithKey();
props.navigation.navigate('MainScreen');
};
configure();
}, []);
//.... more code below....I've tried adding privateKey into the array dependencies which just caused an infinite loop, and I've checked that the value has updated in the redux store - so I'm a bit lost! In essence, it appears that the useSelector hook isn't getting a fresh value. Any help would be very much appreciated 😊 Thanks!
I am trying to do something similar to componentDidMount and call getData action when the page renders. I cannot figure how to do this with Redux Hooks. If I uncomment getUser(userId); it will do an infinite loop.
import React, { useEffect } from 'react';
import { connect, useSelector, useDispatch } from 'react-redux';
import { getData, getUserId } from '../../actions';
import './homepage.scss';
const Main = props => {
const dispatch = useDispatch();
const userId = { auth0_user_id: localStorage.getItem('user_id') };
const user = useSelector(store => store.getUser.user);
console.log(userId);
console.log(user);
const getUser = user => dispatch(getUserId(user));
// getUser(userId);
const logout = e => {
localStorage.removeItem('access_token');
localStorage.removeItem('id_token');
localStorage.removeItem('expires_at');
localStorage.removeItem('user_id');
props.history.push('/login');
};
return (
<div>
<h1>Welcome to a Protected Page!</h1>
{props.users
? props.users.map(user => (
// map over the state of users
<h3>{user.auth0_user_id}</h3>
))
: null}
<button onClick={logout}>Log Out</button>
</div>
);
};
export default Main;