0

I want to make checkboxes controlled, handleChange does the job and toggles the completed property but react doesn't rerender the dom I suppose. How do I make it work?

import React from "react";

export default function App() {
  const [items, setItems] = React.useState([
    { title: "Item 1", completed: false, id: 1 },
    { title: "Item 2", completed: false, id: 2 },
    { title: "Item 3", completed: false, id: 3 }
  ]);

  function handleChange(index) {
    const itemsRef = items;
    itemsRef[index].completed = !itemsRef[index].completed;

    setItems(itemsRef);
  }

  return (
    <div id="app">
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            <input
              type="checkbox"
              checked={item.completed}
              onChange={() => handleChange(index)}
            />
            <label>{item.title}</label>
          </li>
        ))}
      </ul>
    </div>
  );
}

1 Answer 1

2

useState is not like this.setState and will not re-render on mutations, you have to create a copy of the array before you mutate it

function handleChange(index) {
  const itemsRef = [...items];
  itemsRef[index].completed = !itemsRef[index].completed;
  setItems(itemsRef);
}

This is enough for React to re-render even if we mutate the inner object.

But, it is recommended to use the callback pattern and do a complete immutable update if you need to compute the next state based on the previous state because useState updates are always asynchronous

function handleChange(index) {
  setItems(prevState => prevState.map((obj, i) =>
    i === index 
      // create a new object and spread all of the properties
      // then toggle the completed value based on the previous state
      ? { ...obj, completed: !obj.completed }
      : obj
  ))
}

You can also write the above like this

function handleChange(index) {
  setItems(prevState => [
    ...prevState.slice(0, index),
    // create a new object and spread all of the properties
    // then toggle the completed value based on the previous state
    {
      ...prevState[index], 
      completed: !prevState[index].completed
    },
    ...prevState.slice(index + 1)
  ])
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you. As simple as that!
@datogio np, I've updated the answer and added another pattern of immutable updates

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.