skip to Main Content

I’m doing this exercise: https://www.reacterry.com/portal/challenges/select-all-checkboxes

If you look under the checkOnChange function I tried doing it in multiple different ways but they don’t seem to work and I want to really understand why.

I’m just confused about the setCheckboxes part.

Theirs:

setCheckboxes(
    checkboxes.map((checkbox) =>
      checkbox.id === id
        ? { ...checkbox, checked: !checkbox.checked }
        : checkbox
    )
  );

Mine:


//WHY DOESNT THIS WORK?
    const arr = checkboxes;
    const index = arr.findIndex((x) => x.id === id);
    console.log(index);
    console.log(arr);
    arr[index].checked = !arr[index].checked;
    setCheckboxes(arr);
    console.log(checkboxes);

    //WHY DOESNT THIS WORK?
    setCheckboxes((prevState) => {
      const arr = prevState;
      const index = arr.findIndex((x) => x.id === id);
      console.log(index);
      console.log(arr);
      arr[index].checked = !arr[index].checked;
      return arr;
    });


    //WHY DOESNT THIS WORK?
    setCheckboxes(
      checkboxes.forEach((checkbox) => {
        if (checkbox.id === id) {
          checkbox = { ...checkbox, checked: !checkbox.checked }
        }
      })
    );`

THE SOLUTION that I don’t fully understand the difference between my way and this way:

import React, { useState } from 'react';
import styled from 'styled-components';

const CheckboxList = () => {
const [checkboxes, setCheckboxes] = useState([
  { id: 1, label: 'Dogs', checked: false },
  { id: 2, label: 'Cats', checked: false },
  { id: 3, label: 'Cows', checked: false },
  { id: 4, label: 'Deers', checked: false },
]);

const handleCheckboxChange = (id) => {
  setCheckboxes(
    checkboxes.map((checkbox) =>
      checkbox.id === id
        ? { ...checkbox, checked: !checkbox.checked }
        : checkbox
    )
  );
};

const handleSelectAll = () => {
  setCheckboxes(checkboxes.map((checkbox) => ({ ...checkbox, checked: true })));
};

return (
  <Container>
    <CheckboxContainer data-testid="checkbox-container">
      {checkboxes.map((checkbox, index) => (
        <CheckboxLabel key={checkbox.id}>
          <input
            data-testid={`checkbox-${index + 1}`}
            type="checkbox"
            checked={checkbox.checked}
            onChange={() => handleCheckboxChange(checkbox.id)}
          />
          {checkbox.label}
        </CheckboxLabel>
      ))}
    </CheckboxContainer>
    <SelectAllButton data-testid="button" onClick={handleSelectAll}>Select All</SelectAllButton>
  </Container>
);
};

export default CheckboxList;

const Container = styled.div`
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  margin: 24px;
`;

const CheckboxContainer = styled.div`
  display: flex;
  flex-direction: column;
  gap: 5px;
`;

const CheckboxLabel = styled.label`
  display: flex;
  align-items: center;
  gap: 5px;
`;

const SelectAllButton = styled.button`
  padding: 10px 20px;
  font-size: 18px;
  border: none;
  border-radius: 4px;
  background-color: #333;
  color: #fff;
  cursor: pointer;
  margin-top: 24px;

  &:hover {
    opacity: 0.8;
  }
`;

MY CODE IS BELOW:

import React, { useState, useEffect } from 'react';
import styled from 'styled-components';

const CheckboxList = () => {
  const [checkboxes, setCheckboxes] = useState([
    { id: 1, label: 'Dogs', checked: false },
    { id: 2, label: 'Cats', checked: false },
    { id: 3, label: 'Cows', checked: false },
    { id: 4, label: 'Deers', checked: false },
  ]);

  const checkedOnChange = (id) => {

    //WHY DOESNT THIS WORK?
    const arr = checkboxes;
    const index = arr.findIndex((x) => x.id === id);
    console.log(index);
    console.log(arr);
    arr[index].checked = !arr[index].checked;
    setCheckboxes(arr);
    console.log(checkboxes);

    //WHY DOESNT THIS WORK?
    setCheckboxes((prevState) => {
      const arr = prevState;
      const index = arr.findIndex((x) => x.id === id);
      console.log(index);
      console.log(arr);
      arr[index].checked = !arr[index].checked;
      return arr;
    });


    //WHY DOESNT THIS WORK?
    setCheckboxes(
      checkboxes.forEach((checkbox) => {
        if (checkbox.id === id) {
          checkbox = { ...checkbox, checked: !checkbox.checked }
        }
      })
    );
  };

  return (
    <Container>
      <CheckboxContainer data-testid="checkbox-container">
        {checkboxes.map((checkbox, index) => (
          <CheckboxLabel key={checkbox.id}>
            <input
              data-testid={`checkbox-${index + 1}`}
              type="checkbox"
              checked={checkbox.checked}
              onClick={() => {
                checkedOnChange(checkbox.id);
              }}
            />
            {checkbox.label}
          </CheckboxLabel>
        ))}
      </CheckboxContainer>
      <SelectAllButton data-testid="button">Select All</SelectAllButton>
    </Container>
  );
};

export default CheckboxList;

const Container = styled.div`
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  margin: 24px;
`;

const CheckboxContainer = styled.div`
  display: flex;
  flex-direction: column;
  gap: 5px;
`;

const CheckboxLabel = styled.label`
  display: flex;
  align-items: center;
  gap: 5px;
`;

const SelectAllButton = styled.button`
  padding: 10px 20px;
  font-size: 18px;
  border: none;
  border-radius: 4px;
  background-color: #333;
  color: #fff;
  cursor: pointer;
  margin-top: 24px;

  &:hover {
    opacity: 0.8;
  }
`;

I tried 3 different ways to do it and I’m not sure why it doesn’t work. I’d like to fully understand why. And see a solution that is written in the simplest code possible so I can reference it. When I say simple I mean preferably without fancy syntax or the spread operator. Just to get an idea. Thanks guys.

2

Answers


  1. In your version:

    setCheckboxes((prevState) => {
      const arr = prevState;
      const index = arr.findIndex((x) => x.id === id);
      console.log(index);
      console.log(arr);
      arr[index].checked = !arr[index].checked;
      return arr;
    });
    

    The arr being returned is the same array instance as prevState. The data in that array has been mutated (which one should not do in React state), and the array itself is the same reference (which means React doesn’t see that state has changed).

    The same is essentially true in all of your versions. You’re drilling down into the array and modifying its contents, and returning the same array.

    Contrast this with their version:

    setCheckboxes(
      checkboxes.map((checkbox) =>
        checkbox.id === id
          ? { ...checkbox, checked: !checkbox.checked }
          : checkbox
      )
    );
    

    The first thing to notice here is that the return value of .map() is set to the new state, and .map() creates a new array. So React will observe that the array reference is different and state has been updated.

    The second thing to notice is that this array is made up of two things:

    1. For any element which doesn’t match the condition, use that same element (object reference)
    2. For any element which does match the condition, create and return a new object instance (rather than mutate the existing instance), populated with all properties of the existing instance (the spread operator) and one new (or replaced) property

    So overall the takeaway here is that when updating state you should always create new instances of any object or array which needs to be modified, never modify the current one(s).

    Login or Signup to reply.
  2. You are trying to modify the same object in memory via running

    arr[index].checked = !arr[index].checked;
    

    The react way is functional: you pass an object to a function => you get the result. No outer modifications of the argument in between of the function call are expected, which can lead to odd bugs.

    A good rule of thumb: if you need to pass a modified version of an object to setHook, create a new one, so it does not affect the old data flows/execution.

    1. Array.map() creates a new array object, so the checkboxes.map(...) is used.

    2. { ...checkbox, checked: !checkbox.checked } also creates a new object.

    3. React subscribes to object changes in setState. Array in your case did not change (the same memory address), so React thinks that there was not state changes.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search