5

I'm trying to wrap my head around using hooks and I'm running into a repeating problem. In the example I've linked below, I have a useEffect hook that's sorting an array. The sorting order is determined by a state value and I have a button that toggles that state value.

Everything is working that way I intended, the array is sorted when the component mounts, and then on the button click.

However, I'm getting a error from the linter that the array of values needs to be declared as a dependency in the useEffect hook. If I do that, I get a 'Maximum update depth exceeded' error. I'm unsure what to do, and I would appreciate any help!

Link to code

Thanks for taking a look, it really means a lot to me!

norbitrial
  • 14,716
  • 7
  • 32
  • 59

3 Answers3

2

I don't see any issues with your code. What I would do is just simply ignore the warning by adding // eslint-disable-next-line react-hooks/exhaustive-deps as the following:

useEffect(() => {
    function sortValues() {
      let sorted;
      const array = [...values];
      if (ascValue) {
        sorted = array.sort((a, b) => a - b);
      } else {
        sorted = array.sort((a, b) => b - a);
      }

      setValues(sorted);
    }

    sortValues();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ascValue]);

Maybe one shortening on sortValues():

function sortValues() {
    const compare = ascValue ? (a, b) => a - b : (a, b) => b - a;
    setValues([...values].sort(compare));
}

I hope that helps!

norbitrial
  • 14,716
  • 7
  • 32
  • 59
  • Thanks for getting back to me! Your suggestions about shortening are on point, I will apply them. I'm new to learning hooks and so I want to be clear that I'm not challenging you, but I'm looking for more clarification. Is there a rule for disabling the linter via the comment you've posted? It seems like this is a solution, and in fact one that I've played with, but I wonder about straying from the suggested approach. Thanks again! – Nic Bernard Jan 05 '20 at 21:16
  • @NicBernard Unfortunately in this case if you put `values` in your dependency array the warning message will disappear, but then because of calling `setValues()` it will start an infinite loop and that's why you are getting that error message. The other way is add the suggested comment to ignore the message. I think this is the only solution for this case now. I hope this clarifies! – norbitrial Jan 05 '20 at 21:20
1

Ideally, you would add some condition/check to see if values already has the value that you want. As setValues appears to be called on every render, which triggers a re-render, times infinity.

Potentially, using something like Lodash's isEqual combined with Lodash's orderBy would do this trick (because sort will mutate the original array).

  const [ascValue, setAscValue] = useState(true);
  const [values, setValues] = useState([
    { id: 10 },
    { id: 5 },
    { id: 12 },
    { id: 1 },
    { id: 2 },
    { id: 900 },
    { id: 602 }
  ]);

  useEffect(() => {
    function sortValues() {
      const sorter = ascValue ? "asc" : "desc";
      if (!_.isEqual(values, _.orderBy(values, ["id"], [sorter]))) {
        console.log("logged");
        setValues(() => _.orderBy(values, ["id"], [sorter]));
      }
    }

    sortValues();
  }, [ascValue, values]);

The point is, useEffect shouldn't set state every on every render by default (it can once render has finished and the user triggers an action).

A fork of your CodeSandbox to demonstrate that the side effect should only update the components state if the condition is met.

Asciant
  • 2,130
  • 1
  • 15
  • 26
  • Thanks for commenting. I tried the approach you suggested and ended up in the same situation. Add 'values' to the useEffect array creates an update loop. [Link to code](https://codesandbox.io/s/quirky-poincare-2tr2x) Please let me know if I've done something wrong. Thanks again! – Nic Bernard Jan 06 '20 at 12:10
  • 1
    Thanks for the code, that helped. I forked a CSB and updated my answer. – Asciant Jan 06 '20 at 19:47
0

It's not suprising - when You adding the values list in the dependency array, the useEffect will execute on every change of the value list - that leads to the infinite loop :)

According to the previous answer of @norbitrial, hiding of the error will not resolve the things, becuase every dependency from scope MUST be declared in the dependencies array, avoiding it can lead to unexpected behaviours.

I would recommend to consider using useReducer in this situation. It will cause that logic of modifications of the dependency array can be moved out of the useEffect hooks' scope - and You will be not forced to attach it as a dependency of useEffect.

  • Thanks for commenting. I'm in a little over my head on this, I appreciate the help. I've gone down the useReducer path briefly but I seemed to be getting the same result. If I fired a dispatch from useEffect, I then had to declare the reducer as a dependency and then I was in an update loop. Clearly I did something wrong. I'll look into it again. – Nic Bernard Jan 06 '20 at 12:12
  • Mateusz, I went back and created a new sandbox: [Link To Code Using Reducer](https://codesandbox.io/s/eager-herschel-65cgl) If you get a minute, will you let me know if you think this is a good approach? Thanks again! – Nic Bernard Jan 06 '20 at 12:27
  • 1
    @NicBernard, it's looking just fine, so looks like You got the idea :) I uncommented some things that You have commented and looks that is working (apart from toggle, becuase You have apply some other logic there): https://codesandbox.io/s/silly-keller-hs22x – Mateusz Falkowski Jan 06 '20 at 14:58