TodoApp.tsx

import React, { Component, MouseEvent, ChangeEvent } from 'react';

// Represents one item in the todo list.
type Item = {
  name: string;
  completed: boolean;
};

// State of the app is the list of items and the text that the user is typing
// into the new item field.
type TodoState = {
  items: Item[];    // existing items
  newName: string;  // mirrors text in the field to add a new name
}

// Enable debug logging of events in this class.
const DEBUG: boolean = false;

// Top-level application that lets the user add/complete items on a todo list
export class TodoApp extends Component<{}, TodoState> {

  constructor(props: {}) {
    super(props);
    this.state = {items: [], newName: ""};
  }

  render = (): JSX.Element => {
    if (DEBUG) console.debug("rendering")

    // Return a UI with all the items and elements that allow them to add a new
    // item with a name of their choice.
    return (
      <div>
        <h2>To-Do List</h2>
        {this.renderItems()}
        <p className="instructions">Check the item to mark it completed.</p>
        <p className="more-instructions">New item:
          <input type="text" className="new-item"
              value={this.state.newName}
              onChange={this.doNewNameChange} />
          <button type="button" className="btn btn-link"
              onClick={this.doAddClick}>Add</button>
        </p>
      </div>);
  }

  // Returns a div for each item in the todo list, with each div containing a
  // label with the name of the item and a checkbox for marking it completed.
  renderItems = (): JSX.Element[] => {
    const items: JSX.Element[] = [];
    // Inv: items contains a div for each item of this.state.items[0 .. i-1]
    for (let i = 0; i < this.state.items.length; i++) {
      if (this.state.items[i].completed) {
        items.push(
          <div className="form-check" key={i}>
            <input className="form-check-input" type="checkbox"
                id={"check" + i} checked={true} readOnly={true} />
            <label className="form-check-label completed" htmlFor={"check" + i}>
              {this.state.items[i].name}
            </label>
          </div>);
      } else {
        items.push(
          <div className="form-check" key={i}>
            <input className="form-check-input" type="checkbox"
                id={"check" + i} checked={false}
                onChange={evt => this.doItemClick(evt, i)} />
            <label className="form-check-label" htmlFor={"check" + i}>
              {this.state.items[i].name}
            </label>
          </div>);
      }
    }
    return items;
  };

  // Called when the user checks the box next to an uncompleted item. The
  // second parameter is the index of that item in the list.
  doItemClick = (_: ChangeEvent<HTMLInputElement>, index: number): void => {
    const item = this.state.items[index];
    if (DEBUG) console.log(`marking item ${item.name} as completed`);

    // Note: we cannot mutate the list. We have to create a new one.
    const items = this.state.items.slice(0, index)
        .concat([{name: item.name, completed: true}])
        .concat(this.state.items.slice(index + 1));
    this.setState({items: items});

    setTimeout(() => this.doItemTimeout(index), 5000); 
  }

  // Called after an item has been removed for 5 seconds.
  doItemTimeout = (index: number): void => {
    const item = this.state.items[index];
    if (DEBUG) console.log(`removing item ${item.name}`);

    // Note: we cannot mutate the list. We have to create a new one.
    const items = this.state.items.slice(0, index)
        .concat(this.state.items.slice(index + 1));
    this.setState({items: items});
  }

  // Called when the user clicks on the button to add the new item.
  doAddClick = (_: MouseEvent<HTMLButtonElement>): void => {
    // Ignore the request if the user hasn't entered a name.
    // (Would be better to disable the button in this case.)
    const name = this.state.newName.trim();
    if (name.length == 0)
      return;

    if (DEBUG) console.log(`adding new item ${name}`);

    const items = this.state.items.concat([{name: name, completed: false}]);
    this.setState({items: items, newName: ""});
  }

  // Called each time the text in the new item name field is changed.
  doNewNameChange = (evt: ChangeEvent<HTMLInputElement>): void => {
    if (DEBUG) console.log(`changeing new name to ${evt.target.value}`);
    this.setState({newName: evt.target.value});
  }
}

Full Code