React CookBook: Check Mark as Completed or Not

Understanding React lifecycle methods

I’ve been following this tutorial for a To Do list in React
The main thing I’ve changed is Todo to ToDo
and then putting all the components on the same page…
Adding new task and deleting them works as expected
But clicking on the check mark should toggle them as complete or not
instead it is throwing an event error.


https://github.com/TurtleWolf/ReactCookBookChapter_03
ToDo.js

import React, { Component } from 'react';
import uuidv4 from 'uuid/v4';
import List from './List';
import './ToDo.css';

class ToDo extends Component {
constructor() {
    super();
    // Initial state...
  this.state = {
    task: '',
    items: []
      };
  }

componentWillMount() {
// Setting default tasks...
this.setState({
    items: [
          {
            id: uuidv4(),
            task: 'Pay the rent',
            completed: false
          },
          {
            id: uuidv4(),
            task: 'Go to the gym',
            completed: false
          },
          {
            id: uuidv4(),
            task: 'Do my homework',
            completed: false
          }
        ]
      });
  }

  handleOnChange = e => {
    const { target: { value } } = e;
    
    // Updating our task state with the input value...
        this.setState({
    task: value
        });
      }

  handleOnSubmit = e => {
    // Prevent default to avoid the actual form submit...
        e.preventDefault();
    
        // Once is submited we reset the task value and we push  
        // the new task to the items array.
    if (this.state.task.trim() !== '') {
          this.setState({
            task: '',
    items: [
              ...this.state.items,
              {
    id: uuidv4(),
    task: this.state.task,
    complete: false
              }
            ]
          });
        }
      }

  markAsCompleted = id => {
    // Finding the task by id...
    const foundTask = this.state.items.find(
          task => task.id === id
        );
            
    // Updating the completed status...
        foundTask.completed = true;
    
    // Updating the state with the new updated task...
        this.setState({
    items: [
            ...this.state.items,
            ...foundTask
          ]
        });
      }

  removeTask = id => {
    // Filtering the tasks by removing the specific task id...
    const filteredTasks = this.state.items.filter(
          task => task.id !== id
        );
    
    // Updating items state...
        this.setState({
          items: filteredTasks
        });
      }      

render() {
return (
      <div className="ToDo">
        <h1>New Task:</h1>
        <form onSubmit={this.handleOnSubmit}>
          <input 
            value={this.state.task} 
            onChange={this.handleOnChange} 
          />
        </form>

        <List
items={this.state.items}
markAsCompleted={this.markAsCompleted}
removeTask={this.removeTask}
        />
      </div>
    );
  }
}

export default ToDo;

List.js

import React from 'react';

const List = props => (
  <ul>
    {props.items.map((item, key) => (
      <li 
        key={key} 
        className={`${item.completed ? 'completed' : 'pending'}`}                        
      >
        {/* 
          * If the task is completed we assign the 
* .completed class otherwise .pending
          */}
        {item.task}

        <div className="actions">
          {/* 
            * Using a callback on the onClick we call our 
            * markAsCompleted function 
            */}
          <span 
            className={item.completed ? 'hide' : 'done'} 
            onClick={() => props.markAsCompleted(item.id)}
          >
<i className="fa fa-check"></i>
          </span>

          {/* 
            * Using a callback on the onClick we call 
            * our removeTask function 
            */}
          <span 
            className="trash" 
            onClick={() => props.removeTask(item.id)}
          >
<i className="fa fa-trash"></i>
          </span>
        </div>
      </li>
    ))}
  </ul>
);

export default List;

Hi @TurtleWolf1, there are actually a couple of issues with that markAsCompleted method:

  • You’re attempting to spread an object into an array, which yields that “foundTask is not iterable” error – what would work is

    this.setState({
      items: [
        ...this.state.items,
        {...foundTask}
      ]
    })
    
  • However, this way you would append the modified item to the list, not modify it in place;

  • And either way, you’re still mutating the original item and only making a copy afterwards

So try this instead:

markAsCompleted = id => {
  // First, make a copy of the existing items
  const [...items] = this.state.items
  // Then find the index of the item you want to modify
  const index = items.findIndex(task => task.id === id)
  // Now you can replace the item with a modified copy of the found item
  const foundItem = items[index]
  items[index] = { ...foundItem, completed: !foundItem.completed }
  // Finally, set the items to the modified copy of the original array
  this.setState({ items });
}

BTW also note the warning regarding componentWillMount().

Perfect, works like a charm and good to know about the warning too, since the whole section is about life cycle components.
Next I’m going to try a ternary to toggle it back on.

1 Like