TODO-App with React and Typescript (Part 2/2)

After the initial project setup in the first part of this tutorial, we can finally start with the app. To keep things simple, this tutorial will cover only the basics functionality to create and list "ToDo"-Items.

Let's define a class for a ToDo item:

export default class ToDo {
    constructor(
        public id: number,
        public created: Date,
        public text: string,
        public done: boolean) {
    }
}

This class should be pretty self-explanatory: The id property is unique for all created ToDo-Items. We can use automatic assignment of constructor parameters in TypeScript to spare some lines of code. In this case all properties of the ToDo class are defined with the public modifier in the constructor.

In order to display the ToDo-Items, we will implement two UI-components: a list component ToDoList which holds individual ToDoItem-s. Note that to enable the JSX syntax with TypeScript, we have to use the ".tsx" extension for the source files.

import * as React from 'react';
import ToDo from './ToDo';

interface ToDoItemProps {
    todo: ToDo;
    setDone: (todo: ToDo) => void;
}

export default class ToDoItem extends React.Component<ToDoItemProps, ToDo> {

    constructor(props: ToDoItemProps) {
        super(props);
        this.setState({ ...props.todo });
    }

    render(): JSX.Element {
        return (
            <div className="ToDoItem">
                {this.state.text}
                <span>{this.state.created}</span>
            </div>
        );
    }
}

To better illustrate the TypeScript (with embedded jsx) syntax, this component is defined as a class component:

  • it extends the React.Component class,
  • accepts predefined properties (ToDoItemProps)
  • and implements the render method

The ToDoItem recives ToDoItemProps with an ToDo object and a callback function to mark the ToDo as done. Since the ToDo object can be modified within this widget, it will be assigned to ToDoItem-s state:

this.setState({ ...props.todo });

We could define an interface to for the state in the ToDoItem, but for this case ToDo is sufficient.

In the last step we will provide the container component for the ToDoItem-s and wire the app together:

interface ToDoListProps {
    todos?: Array<ToDo>
}

interface ToDoListState {
    todos?: Array<ToDo>
}

export default class ToDoList extends React.Component<ToDoListProps, ToDoListState> {

    constructor(props: ToDoListProps) {
        super(props);
        this.state = { ...props };
    }

    setDone(todo: ToDo): void { 
        const todos = this.state.todos;
        todos.splice(todos.indexOf(todo), 1);
        this.setState({todos});
    }

    addToDo(): void { ... }

    render(): JSX.Element {
        const todos = this.state.todos || [];
        return (
            <div className="ToDoList">
                <ul>
                { todos.length > 0 && 
                  todos.map(todo => 
                    <li key={todo.id}>
                        <ToDoItem todo={todo} setDone={() => this.setDone(todo)}/>
                    </li>
                )}

                { todos.length < 1 && 
                    <li>All ToDos are done!</li>
                }
                </ul>

                <button onClick={() => this.addToDo()}>Create ToDo</button>
            </div>
        );
    }
}

The list component ToDoList is again a very simple React class component: in the props it receives a list of ToDo-s and displays them in a list with ToDoItem. Since the list can be changed within the ToDoList, we also have to define a state which is the same as the props in this case.

The addToDo method is intentionally left out: for a demonstration case, one can create a random ToDo object, add it to the list and call setState to re-render the component.

In the last step we can put everything together in a App component with some predefined ToDo-s objects:

const todosList = [
    new ToDo(1, new Date(), 'DEVLABS.ninja Test Todo 1', false),
    new ToDo(2, new Date(), 'DEVLABS.ninja Test Todo 2', false)
]; 

class App extends React.Component {
  render() {
    return (
      <div className="App">
          <ToDoList todos={ todosList }/>
      </div>
    );
  }
}

The App component doesn't have a state or props: it works only as a wrapper for the list component and provides initial list with ToDo-s.

Resources: