简体   繁体   中英

Checkbox not changing state in React component

I am creating a todolist app using React. The data for the todos look like the following:

const todoData = [
    {
        id: 1,
        text: "Empty bin",
        completed: true
    },
    {
        id: 2,
        text: "Call mom",
        completed: false
    }
]

Now, I have an App component where I import that data and save it in state.

import todoData from "./todoData"

class App extends React.Component {
  constructor() {
    super()
    this.state = {
      todos: todoData,
    }

    this.handleChange = this.handleChange.bind(this)
  }
  ...

I also have an handleChange method which is supposed to change the value of the completed property to its inverse value. For example: for the todo with an id of 1, it's text value is "Empty Bin" and completed is true so by default the checkbox would be checked. However, when it is clicked, completed should be false and the checkbox should no longer be clicked. For some reason, this does not happen, so completed stays at its default boolean value and doesn't flip. So when a checkbox is clicked no change happens.

handleChange(id) {
    this.setState(prevState => {
      const updatedTodos = prevState.todos.map(todo => {
          if (todo.id === id) {
            todo.completed = !todo.completed
          }
          return todo
        })

        return {
          todos: updatedTodos
        }
      })
}

After using console.log I realized that todo.completed is indeed being changed to its opposite value, but for some reason, it is not changed in updatedTodos even though in devtools map() says the value was updated when it return a new array which was stored in updatedTodos. Hence, the state does not change and the checkbox can't be clicked

The TodoItem functional component is in a separate file from the App component and contains the HTML for the the todo elements. It is shown below:

function TodoItem(props) {
    return (
        <div className="todo-item">
            <input type="checkbox"
            checked={props.task.completed}
            onChange={() => props.handleChange(props.task.id)}/>
            <p>{props.task.text}</p>
        </div>
    )
}

Also, the TodoItem was rendered in the App component

render() {
    const todoArray = this.state.todos.map(task => <TodoItem key={task.id} 
      task={task} handleChange={this.handleChange}/>)
    return (
      <div className="todo-list">
        {todoArray}
      </div>
    )
  }

Looks like you are mutating the todo object inside handleChange function

handleChange(id) {
    this.setState(prevState => {
      const updatedTodos = prevState.todos.map(todo => {
          if (todo.id === id) {
            //todo.completed = !todo.completed this is mutating
            // in react mutating a object will result in unexpected results like this.
            // so you have to create a new object based on the current todo and return it

              return {
                  ...todo,
                  completed: !todo.completed
              }
          }
          return todo
        })

        return {
          todos: updatedTodos
        }
    })
}

What's happening there?

Each object in the array is pointed to a memory location, basically if we change the object property values (for EX: completed ), without changing the memory location it's mutating,

And by doing todo.completed = !todo.completed we directly change the value of completed property but the todo object still pointed to the same memory location, so we mutate the object and react will not respond to it, and now by doing this

 return {
    ...todo,  // <- create a new object based on todo
    completed: !todo.completed  // <- change the completed property
}

we create a new object based on the todo {...todo} (new object = new memory location), and we change the value of completed => {...todo, completed: !todo.completed} , since this is pointed to new memory location react will respond to the changes.

if you are not familiar with the spread operator ( ... ), read it here , don't forget to check out Spread in object literals section there

You need to spread your objects then mutate particular field

        if (todo.id === id) {
            return {
              ...todo,
              completed: !todo.completed
            }
          }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM