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.
My understanding of
useEffect
monitoring 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]);