2

I am new to React and I am very confused about the lifecyle of a functional component. Let's say I have many inputs of type: checkbox, a, b, c and d. Their values are a, b, c and d.

const OnBoarding = () => {
  const [isChecked, setIsChecked] = useState({
    a: false,
    b: false,
    c: false,
    d: false
  });
  
  const [allCheckedItems, setAllCheckedItems] = useState([]);
  
  //Write function that pushes checked value to state array when clicked
  const onChecked = e => {
    setIsChecked({
      ...isChecked,
      [e.target.value]: e.target.checked
    });
    const all = Object.keys(isChecked).filter(function(key){
      return isChecked[key] === true;
    });
    console.log("all items that are TRUE : " + all);
    setAllCheckedItems([all]);
    console.log("allCheckedItems : " + allCheckedItems);
  };
  
  useEffect(() => {
    console.log(isChecked);
  });
  
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

I have a functional component with a state whose type is an object with many properties, all booleans, like so:

const [isChecked, setIsChecked] = useState({
  a: false,
  b: false,
  c: false,
  d: false
})

I have an other state that is an array, let's call it allCheckedItems, that is supposed to be an array of all the keys of isChecked set to true.

const [allCheckedItems, setAllCheckedItems] = useState([]);

With a function onChecked, when a checkbox is checked, it's value in the isChecked object sets to true.

I call useEffect() in which I wrote a filter function to filter all properties that are true. All the properties set to true are store in an array called "all".

My problem comes when I have to update the state of the array allCheckedItems. If I write in the useEffect hook:

setAllCheckedItems([all]);

I get an infinite loop. If I write it in my onChecked function, whenever I click on a checkbox, the state of allCheckedItems that I log in the console is LATE of one click. Like, I click to check "a" and it logs:

[]

Then I click on "b" and it logs:

[a]

Then I click on "c" and it logs:

[a, b]

I would like that when I check a box, both states update and that allItemsChecked logs all items checked immediately... Can someone explain to me this behavior? It has given me headaches for days now.

wentjun
  • 40,384
  • 10
  • 95
  • 107
Jasmine
  • 45
  • 1
  • 7
  • Does this answer your question? [Infinite loop in useEffect](https://stackoverflow.com/questions/53070970/infinite-loop-in-useeffect) – Matt Jan 07 '20 at 17:42

1 Answers1

3

The reason why the useEffect hook gives you an infinite loop is because useEffect is triggered after every render when it is not supplied with the second argument. This is stated on the official react documentation for react hooks, and that the component will skip the applying of the effect if the values within the second argument have not changed.

While calling setAllCheckedItems() within your useEffect() can work if we set [isChecked] as the second argument of useEffect() (which will ensure that the effect will only be applied if the values of isChecked has changed),

useEffect(() => {
  setAllCheckedItems([all]);
}, [isChecked]);

I would recommend you to reduce the complexity of the code by simply calling setAllCheckedItems() at your onClick event of your checkbox, since it is to be run everytime the checkbox is checked. You can refactor the onChecked method such that setAllCheckedItems() is not reliant on the isChecked state.

const onChecked = (e) => {
  // get current checked state, and update it with the checkbox values
  const updatedChecked = {
    ...isChecked,
    [e.target.value]: e.target.checked
  }
  // update isChecked state
  setIsChecked(updatedChecked);
  const all = Object.keys(updatedChecked).filter(function(key){
    return updatedChecked[key] === true;
  });
  // update allCheckedItems state
  setAllCheckedItems([all]);
}

As you can see, we have created a copy of the isChecked state, and update the copy with the new values. In addition, we use that copy to return the keys that are checked. After which, we update the respective states.

wentjun
  • 40,384
  • 10
  • 95
  • 107
  • Thanks. I already called the setAllCheckedItems in my onChange event of my checkbox, in the function called onChecked, as you can see. I still see the array empty when I first click, and then one step behind, all the time – Jasmine Jan 07 '20 at 17:43
  • @Jasmine yes, I understand. But you can definitely modify your logic such that `setAllCheckedItems()` is not reliant on the `isChecked` state. – wentjun Jan 07 '20 at 17:45
  • It worked! Although, I have no idea why. But thanks! – Jasmine Jan 07 '20 at 17:57
  • @Jasmine glad to help! setting of state is asynchronous, that is why the state does not get updated immediately. To overcome this problem, I created a copy of the state at the start of the method, and carry out the necessary logic, before updating the `isChecked ` and `allCheckedItems ` states with the new values. – wentjun Jan 07 '20 at 18:00
  • Thanks, it helps a lot! – Jasmine Jan 07 '20 at 18:26