1

I want to change the value of formStepTouched when the value for step changes, so I am using useEffect. But useEffect throws a warning that its missing dependency of formStepTouched. As that is the value that's changing, putting that in the dependency array would cause infinite calls.

 const [step, setStep] = useState(0);
 const [formStepTouched, setFormStepTouched] = useState(
    Array(childrenArray.length)
      .fill(false)
      .map((_, idx) => idx === 0)
 )

 useEffect(() => {
    const newFormStepTouched = [...formStepTouched];
    newFormStepTouched[step] = true;
    setFormStepTouched(newFormStepTouched);
  }, [step]);

Please refer to the codesandbox link below: https://codesandbox.io/s/brave-gould-ymjt0?file=/src/App.js

As you can see the demo works perfectly however an error message is shown. If the dependecy is added useEffect will be called infinitely.

How to get rid of the error message.

dracarys
  • 1,071
  • 1
  • 15
  • 31

2 Answers2

1
   useEffect(() => {
    setFormStepTouched(prevState =>  {
      const newFormStepTouched = [...prevState]
      newFormStepTouched[step] = true
      return newFormStepTouched
    });
  }, [step]);

The linter is telling you that have an external dependency that may change, you can read more here

K.P
  • 444
  • 3
  • 6
1

You can pass a callback to state setter:

const { useEffect, useState } = React;

const App = () => {
  const [step, setStep] = useState(0);
  const [formStepTouched, setFormStepTouched] = useState(
    Array(5)
      .fill(false)
      .map((_, idx) => idx === 0)
  );

  useEffect(() => {
    //pass a callback to state setter
    setFormStepTouched((formStepTouched) => {
      if (formStepTouched[step] === true) {
        //nothing to change
        return formStepTouched;
      }
      return formStepTouched.map((s, i) =>
        i === step ? true : s
      );
    });
  }, [step]);

  return (
    <div>
      <button onClick={() => setStep(step + 1)}>
        nex step
      </button>
      <div>
        <pre>
          {JSON.stringify(formStepTouched, undefined, 2)}
        </pre>
      </div>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
HMR
  • 37,593
  • 24
  • 91
  • 160
  • I might be wrong, but does this not mutate the state. If you pass a callback, is it ok to mutate the state like this? – dracarys Jul 23 '20 at 12:50
  • @dracarys The now deleted answer with `prevState[step] = true` will mutate state if you try it in your sandbox you'll see steps array is one step behind current step because the effect did not cause the component to re render. Array map creates a new array and doesn't mutate. You could do `const copy = [...current]; copy[step]=true;return copy;` as well. – HMR Jul 23 '20 at 12:53
  • thanks for your explanation but I am not sure I understand completely. formStepTouched is still pointing to the same location in memory right? Or is it because it is being passed in the argument, React internally takes care of it and passed a argument that is pointing to a different location? – dracarys Jul 23 '20 at 12:58
  • @dracarys At what line do you think I'm mutating `formStepTouched`? – HMR Jul 23 '20 at 12:59
  • I just went through your code again, I dont think you are, I got confused earlier. Thanks for the help. – dracarys Jul 23 '20 at 13:07