DEV Community

Cover image for Create a todo list in React
Aditya Chukka
Aditya Chukka

Posted on • Edited on • Originally published at achukka.Medium

Create a todo list in React

In this tutorial we will learn how to create a todo list in React using Typescript.

3_app_with_todo_list

Before we create the application, let’s set up our development environment.

  1. Download and install the latest stable version of Node

Though Node is not really required to use React, it bundle react applications into a neat packages that can be used relatively easy by other clients. See this stack over flow post for more details

Section 1: Create a react application

Open your terminal and run

npx create-react-app todolist — template typescript

Yes, you read it right, npx is a tool for executing node binaries. This stack overflow post describes the differences well

Once you run the above command completes, your project structure should look like this

1_folder_structure

Now you can run your project by doing

npm start

You should see your application running on your default browser at port 3000.

If you have some other application react would ask you to allow it to run on the next availability port

2_initial_run

Congratulations 👏 , you have successfully created your first react application.

Apologies if you are already an expert on React

Please commit your code to GitHub or any other code hosting platform. You can refer to this commit for code structure.


In this section, we will build a component to display items in tabular format

Feel free to skip this section by jumping this gist

Section 2.1: Define an interface to represent an item in the todo list

We store the task we are interested in doing as string and it’s priority as number.

export interface Item {
  task: string;
  priority: number;
}
Enter fullscreen mode Exit fullscreen mode

Section 2.2: Define a component to show the items

This component will receive the items it needs to display through props. Let’s call it ToDoList

In the render method we collect the items for props. If there are no items received return a text, Ex: Empty List.

class ToDoList extends React.Component<{ items: Item[] }, {}> {
  render() {
    const { items } = this.props;

    if (items.length === 0) {
      return <div>Empty List</div>;
    }
  }
Enter fullscreen mode Exit fullscreen mode

React.Component takes props as first argument and state as second variable

Since the above component does not involve any user interaction we don’t have to store any state. Hence we can ignore the constructor.

If there are any items, we present in tabular format. First create a table with a header.

<table {...getTableStyleProps()}>
  <thead>
    <tr key={`task_prioirity`}>
      <th>Task</th>
      <th>Priority</th>
    </tr>
  </thead>
</table>
Enter fullscreen mode Exit fullscreen mode

The key property in the row element would be used by React to decide whether this row needs to be re-rendered when there is a change in this component. React docs has a pretty good explanation on keys

Construct the table body by iterating items using map and creating a row

<tbody>
  {items.map((i, index) => (
    <tr
      key={`${i.task}_${i.priority}`}
      style={{ backgroundColor: index % 2 === 0 ? "#dddddd" : "white" }}
    >
      <td>{i.task}</td>
      <td>{i.priority}</td>
    </tr>
  ))}
</tbody>
Enter fullscreen mode Exit fullscreen mode

It would be better if we organize our items based on priority. Hence we sort them in ascending order

const sortItems = (items: Item[]): Item[] => {
  return items.sort((i1, i2) => i1.priority - i2.priority);
};
Enter fullscreen mode Exit fullscreen mode

Stitching everything together we get our ToDoList Component

// ToDoList.tsx
import React from "react";

export interface Item {
  task: string;
  priority: number;
}


const getTableStyleProps = (): {} => {
  return {
    style: {
      width: "100%",
      fontFamily: "arial, sans-serif",
      borderCollapse: "collapse",
      textAlign: "left",
      padding: "8px",
      border: " 1px solid #dddddd",
    },
  };
};


class ToDoList extends React.Component<{ items: Item[] }, {}> {
  render() {
    const { items } = this.props;

    if (items.length === 0) {
      return <div>Empty List</div>;
    }
    const sortedItems = sortItems(items);
    return (
      <table {...getTableStyleProps()}>
        <thead>
          <tr key={`task_prioirity`}>
            <th>Task</th>
            <th>Priority</th>
          </tr>
        </thead>
        <tbody>
          {sortedItems.map((i, index) => (
            <tr
              key={`${i.task}_${i.priority}`}
              style={{ backgroundColor: index % 2 === 0 ? "#dddddd" : "white" }}
            >
              <td>{i.task}</td>
              <td>{i.priority}</td>
            </tr>
          ))}
        </tbody>
      </table>
    );
  }
}

const sortItems = (items: Item[]): Item[] => {
  return items.sort((i1, i2) => i1.priority - i2.priority);
};

export default ToDoList;
Enter fullscreen mode Exit fullscreen mode

Section 3: Add ToDoList to App

Feel free to skip this section by jumping this gist

At this point, we are ready to use the ToDoList component we wrote in the previous subsection.

Import the component and build an initial list of items

import React from "react";
import ToDoList, { Item } from "./ToDoList";

const initialList = [
  {
    task: "Pick up Milk",
    priority: 1,
  },
  {
    task: "Buy Eggs",
    priority: 2,
  },
  {
    task: "Buy Bread",
    priority: 3,
  },
];
Enter fullscreen mode Exit fullscreen mode
  • Extend the App component to accept props and items as state.
  • Pass items received through state to ToDoList component in render method

class App extends React.Component<{}, { items: Item[] }> {
  constructor(props: any) {
    super(props);
    this.state = {
      items: initialList,
    };
  }

  render() {
    const { items } = this.state;
    return (
      <div className="App">
        <br />
        <ToDoList items={items} />
      </div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Stitching everything together should give us our App component

// App.tsx
import React from "react";
import ToDoList, { Item } from "./ToDoList";

const initialList = [
  {
    task: "Pick up Milk",
    priority: 1,
  },
  {
    task: "Buy Eggs",
    priority: 2,
  },
  {
    task: "Buy Bread",
    priority: 3,
  },
];

class App extends React.Component<{}, { items: Item[] }> {
  constructor(props: any) {
    super(props);
    this.state = {
      items: initialList,
    };
  }

  render() {
    const { items } = this.state;
    return (
      <div className="App">
        <br />
        <ToDoList items={items} />
      </div>
    );
  }
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Running the application by npm start should show a table like below

12_todoList_display

Please remember to commit your changes at this point.


Section 4: Define a component to add a new item

Feel free to skip section by jumping to this gist

This component would contain two text boxes, one for task and another for priority and a button to submit the item. Let’s call it AddItem

I sincerely apologize for bad naming, open for feedback on these

For this component we would need to store the input entered by user in a state variable

import React from "react";
import { Item } from "./ToDoList";

class AddItem extends React.Component<{ addItem: any }, Item> {
  constructor(props: any) {
    super(props);
    this.state = {
      task: "",
      priority: -1,
    };   
  }
Enter fullscreen mode Exit fullscreen mode

Render the input form in a tabular format

render() {
  return (
    <table>
      <tbody>
        <tr key={""}>
          <td>Task:</td>
          <td>
            <input
              id="task"
              type="text"
              placeholder="Enter task here"
              onChange={this.setTask}
            />
          </td>
          <td>Priority:</td>
          <td>
            <input
              id="prioity"
              type="text"
              placeholder="Enter priority here"
              onChange={this.setPriority}
            />
          </td>
          <td>
            <input id="submit" type="submit" onClick={this.addItem} />
          </td>
        </tr>
      </tbody>
    </table>
  );
}
Enter fullscreen mode Exit fullscreen mode

As you might have already guessed we will use the functions setTask and setPriority to update the state of item.

setTask(evt: any) {
  this.setState({
    task: evt.target.value,
  });
}

setPriority(evt: any) {
  this.setState({
    priority: parseInt(evt.target.value),
  });
}
Enter fullscreen mode Exit fullscreen mode

Once we collected the inputs, we should validate them.

const isValid = (item: Item): boolean => {
  return item.task !== "" && item.priority !== -1;
};
Enter fullscreen mode Exit fullscreen mode

Now we can submit the item using the function addItem

addItem(evt: any) {
  const item = this.state;
  if (isValid(item)) {
    this.props.addItem(item);
  }

  this.setState({
    task: "",
    priority: -1,
  });
}
Enter fullscreen mode Exit fullscreen mode

The above snippet calls a function addItem on props. This would pass state (or data) to the parent component. In react world this strategy is called Lifting State Up. We do this so that AddItem can be reused to create newer items.

For the above three functions to be available in render method we need to bind to this object in the constructor.

class AddItem extends React.Component<{ addItem: any }, Item> {
  constructor(props: any) {
    super(props);
    this.state = {
      task: "",
      priority: -1,
    };
    this.setTask = this.setTask.bind(this);
    this.setPriority = this.setPriority.bind(this);
    this.addItem = this.addItem.bind(this);
  }
Enter fullscreen mode Exit fullscreen mode

Joining everyything together gives us the AddItem component

// AddItem.tsx
import React from "react";
import { Item } from "./ToDoList";

const isValid = (item: Item): boolean => {
  return item.task !== "" && item.priority !== -1;
};

class AddItem extends React.Component<{ addItem: any }, Item> {
  constructor(props: any) {
    super(props);
    this.state = {
      task: "",
      priority: -1,
    };
    this.setTask = this.setTask.bind(this);
    this.setPriority = this.setPriority.bind(this);
    this.addItem = this.addItem.bind(this);
  }

  setTask(evt: any) {
    this.setState({
      task: evt.target.value,
    });
  }

  setPriority(evt: any) {
    this.setState({
      priority: parseInt(evt.target.value),
    });
  }

  addItem(evt: any) {
    const item = this.state;
    if (isValid(item)) {
      this.props.addItem(item);
    }

    this.setState({
      task: "",
      priority: -1,
    });
  }

  render() {
    return (
      <table>
        <tbody>
          <tr key={""}>
            <td>Task:</td>
            <td>
              <input
                id="task"
                type="text"
                placeholder="Enter task here"
                onChange={this.setTask}
              />
            </td>
            <td>Priority:</td>
            <td>
              <input
                id="prioity"
                type="text"
                placeholder="Enter priority here"
                onChange={this.setPriority}
              />
            </td>
            <td>
              <input id="submit" type="submit" onClick={this.addItem} />
            </td>
          </tr>
        </tbody>
      </table>
    );
  }
}

export default AddItem;
Enter fullscreen mode Exit fullscreen mode

Section 5: Add AddItem to App component

Feel free to skip this section by jumping to this gist

AddItem component can be now imported to App

Before adding a new item, we would need to check if it already exists. Let’s write a helper function isPartOf that looks if item is present in items.

const isPartOf = (item: Item, items: Item[]): boolean => {
  return items.some((it) => it.priority === item.priority);
};
Enter fullscreen mode Exit fullscreen mode

Implement addItem using the helper function isPartOf.

  • If item already exists, alert the user
  • Else update the state
addItem(item: Item) {
  const { items } = this.state;

  if (isPartOf(item, items)) {
    alert(`Item with priorirty: ${item.priority} exists`);
    return;
  }
  this.setState({
    items: items.concat(item),
  });
}
Enter fullscreen mode Exit fullscreen mode

We should concatenate the item to the current list, since states are immutable in react

Bind addItem in the App constructor

class App extends React.Component<{}, { items: Item[] }> {
  constructor(props: any) {
    super(props);
    this.state = {
      items: initialList,
    };
    this.addItem = this.addItem.bind(this);
  }
Enter fullscreen mode Exit fullscreen mode

Combining all the code parts together should give us our new App component

// App.tsx
import React from "react";
import AddItem from "./AddItem";
import ToDoList, { Item } from "./ToDoList";

const initialList = [
  {
    task: "Pick up Milk",
    priority: 1,
  },
  {
    task: "Buy Eggs",
    priority: 2,
  },
  {
    task: "Buy Bread",
    priority: 3,
  },
];

const isPartOf = (item: Item, items: Item[]): boolean => {
  return items.some((it) => it.priority === item.priority);
};

class App extends React.Component<{}, { items: Item[] }> {
  constructor(props: any) {
    super(props);
    this.state = {
      items: initialList,
    };
    this.addItem = this.addItem.bind(this);
  }

  addItem(item: Item) {
    const { items } = this.state;

    if (isPartOf(item, items)) {
      alert(`Item with priorirty: ${item.priority} exists`);
      return;
    }
    this.setState({
      items: items.concat(item),
    });
  }

  render() {
    const { items } = this.state;
    return (
      <div className="App">
        <AddItem addItem={this.addItem} />
        <br />
        <ToDoList items={items} />
      </div>
    );
  }
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Your todo list app is ready to used now. Running npm start should bring a window like below

3_app_with_todo_list

Please check this commit for full code.

❤️ Congratulations 👏, you have successfully created a todo list in React.

Congratulations

I have also hosted this application on Code Sandbox. Feel free to play with it

Thanks for reading through the entire article. Please reach out with questions, comments and/or feedback.

Top comments (0)