Now I do not understand why the data value obtained by useSelector
is monitored by useEffect
and useState
is used to render the page in a closed loop.
Here’s my code:
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;
}
});
const [list, setList] = useState<DeviceDataOne[][]>([]);
useEffect(() => {
if (data !== null) {
let arr = [];
for (var i = 0; i < (data as DeviceDataOne[]).length; i += 4) {
arr.push((data as DeviceDataOne[]).slice(i, i + 4));
}
setList(JSON.parse(JSON.stringify(arr)));
} else {
setList([]);
}
}, [data]);
useEffect(() => {
console.log('list', list);
}, [list]);
useEffect(() => {
console.log('data', data);
}, [data]);
My understanding of useEffect
monitoring should be that when the data
is changed, he will re-execute the code in useEffect
. However, I actually found that such writing would lead to infinite re-rendering. Therefore, I can’t understand why there is an endless loop.
I tried to comment the middle piece of code
useEffect(() => {
if (data !== null) {
let arr = [];
for (var i = 0; i < (data as DeviceDataOne[]).length; i += 4) {
arr.push((data as DeviceDataOne[]).slice(i, i + 4));
}
setList(JSON.parse(JSON.stringify(arr)));
} else {
setList([]);
}
}, [data]);
But I don’t understand why I can’t write it like this or what causes such problems.
2
Answers
That’s correct, but
data
is being changed: theuseSelector
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 changesdata
, which triggers the effect again.Your understanding of the
useEffect
hook is correct, but you are not understanding that youruseSelector
hook is potentially returning a new array object reference each time the component renders. In other words,data
is a new reference that triggers theuseEffect
hook to run. TheuseEffect
hook enqueues alist
state update. When React processes thelist
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.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 returneddata
value that will only trigger theuseEffect
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: