Search code examples
reactjsstatesetstate

State is being deleted in React JS


I'm new to React. I'm trying to make a todo app, but every time when I update state, (I want to change some bool values), it gets deleted. Please, can someone explain, what is going on in my code, why it's not deleted in function todoDoneParent. Code:

TodoApp component

class TodoApp extends Component {
  state = {
    todos: [
      {id: 1, body: 'First Todo', isDone: false},
      {id: 2, body: 'Second Todo', isDone: false},
      {id: 3, body: 'Third Todo', isDone: false}
    ]
  }
  todoDoneParent= (id) => {
    let copyState = [...this.state.todos];
    let todo = copyState.filter(todo => todo.id == id );
    todo[0].isDone = true;
    let todos = copyState.filter(todo => todo.id != id );
    this.setState({ todos: todo, todos }); 

    console.log('INSIDE todoDoneParent', this.state.todos);///////////////////////////////FIRST PRINT
  }
  render() {
    console.log('INSIDE TodoApp.render()', this.state.todos); ///////////////////////////////SECOND PRINT
    return (
      <div className="allTodos">
      <Todos allTodos={this.state.todos} todoDoneParent={this.todoDoneParent}></Todos>
      </div>
    );
  }
}

Todos component

class Todos extends Component {
  todoDone = (e) => {
    this.props.todoDoneParent(e.target.id);
  }
  render(){
    const todos= this.props.allTodos;
    const list = todos.length ? (
      todos.map(todo => {
        if(todo.isDone === false){
          return(<div className='todo' key={todo.id}>
            <div className='todo-container'>
              <div>
                <p>{todo.body}</p>
              </div>
              <button className='btn' id={todo.id} onClick={this.todoDone}>
                Done
              </button>
            </div>
          </div>)
        }
      })
    ) : (
      <div>
        <p>No todos</p>
      </div>
    );
    return(
      <div className='content'> 
        {list}
      </div>
    );
  }
}

Outputs (in Console)

When it's just rendered

INSIDE TodoApp.render() 
(3) [{…}, {…}, {…}]
0: {id: 1, body: "First Todo", isDone: false}
1: {id: 2, body: "Second Todo", isDone: false}
2: {id: 3, body: "Third Todo", isDone: false}
length: 3
__proto__: Array(0)

When I clicked any button

INSIDE todoDoneParent
(3) [{…}, {…}, {…}]
0: {id: 1, body: "First Todo", isDone: false}
1: {id: 2, body: "Second Todo", isDone: false}
2: {id: 3, body: "Third Todo", isDone: true}
length: 3
__proto__: Array(0)

INSIDE TodoApp.render() 
(2) [{…}, {…}]
0: {id: 1, body: "First Todo", isDone: false}
1: {id: 2, body: "Second Todo", isDone: false}
length: 2__proto__: Array(0)

So why when I click the button ```Done``` , it updates the state well inside ```todoDoneParent``` function, but, when it is going to be rendered, those state that I updated, gets deleted?

Solution

  • Issue

    this.setState({ todos: todo, todos }); just overwrites this.state.todos with the todos that filtered out the one you wanted to mark done. It removes it from the todos array.

    Another issue is a type comparison issue where the id type of your todos is "number", but the id field coming back from the onClick event is type "string". === uses type equality, i.e. 5 === '5' is false, but 5 == '5'.

    Solution

    If I understand correctly that you simply want to toggle the isDone property of a specific todo to true then you should map the old todos to the new todos and update the specific todo when the id matches.

    TodoApp

    todoDoneParent = (id) => {
      this.setState((prevState) => ({
        todos: prevState.todos.map((todo) =>
          todo.id === id
            ? {
                ...todo,
                isDone: true
              }
            : todo
        )
      }));
    };
    

    Todos

    Convert the id value back to a number type for the comparison in the parent component.

    todoDone = (e) => {
      const { id } = e.target;
      this.props.todoDoneParent(Number(id));
    }