skip to Main Content

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


  1. 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.

    Login or Signup to reply.
  2. My understanding of useEffect monitoring should be that when the
    data is changed, he will re-execute the code in useEffect.

    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]);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search