Строчка
todo.completed = !todo.completed
выполняется при каждом клике дважды, поэтому сначала значение становится false, затем снова true, в то время как текстовая переменная прекрасно меняется. Мне немного стыдно, поскольку точно не знаю механизм работы реакта в данном случае, но выхода тут два:
1)
if (todo.id === id) return { ...todo, completed: !todo.completed }
return todo
Что в действительности более правильно, так как каждый изменяемый элемент в реакте должен менять ссылку при изменении.
2) Вычислять новый completed сразу
onChange={() => props.handleChange(props.item.id, !props.item.completed)}
и уже в функции пользоваться полученным значением
handleChange(id, newCompleted) {
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
todo.completed = newCompleted
}
return todo
})
return {
todos: updatedTodos
}
})