Tutorial

Create a Simple To-Do App With React

Draft updated on Invalid Date
Default avatar

By Chris Nwamba

Create a Simple To-Do App With React

This tutorial is out of date and no longer maintained.

Introduction

As the topic implies, we are going to be building a To-Do application with React. Do not expect any surprises such as managing state with a state management library like Flux or Redux. I promise it will strictly be React. Maybe in the following articles, we can employ something like Redux but we want to focus on React and make sure everybody is good with React itself.

Prerequisites

You don’t need much requirements to setup this project because we will make use of CodePen for demos. You can follow the demo or setup a new CodePen pen. You just need to import React and ReactDOM library:

<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>To-Do</title>
</head>
<body>

  <div class="container">
    <div id="container" class="col-md-8 col-md-offset-2">  </div>
  </div>

  <script src="https://fb.me/react-15.1.0.js"></script>
  <script src="https://fb.me/react-dom-15.1.0.js"></script>

</body>
</html>

ReactDOM is a standalone library that is used to render React components on the DOM.

Types of Components

There are two types of components. These types are not just react-based but can be visualized in any other component-based UI library or framework. They include:

  • Presentation Component
  • Container Component

Presentation Component: These are contained components that are responsible for UI. They are composed with JSX and rendered using the render method. The key rule about this type of component is that they are stateless meaning that no state of any sort is needed in such components. Data is kept in sync using props.

If all that a presentation component does is render HTML based on props, then you can use stateless function to define the component rather than classes.

Container Component: This type of component complements the presentation component by providing states. It’s always the guy at the top of the family tree, making sure that data is coordinated.

You do not necessarily need a state management tool outside of what React provides if what you are building does not have too many nested children and less complex. A To-Do is simple so we can do with what React offers for now provided we understand how and when to use a presentation or container component

Recognizing Presentation Components

It is a recommended practice to have a rough visual representation of what you are about to build. This practice is becoming very important when it comes to component-based designs because it is easier to recognize presentation components.

Todo Sketch

Your image must not be a clean sketch made with a sketch app. It can just be pencil work. The most important thing is that you have a visual representation of the task at hand.

From the above diagram, we can fish out our presentation components:

  • TodoForm : purple
  • Title: green
  • TodoList: red
  • Todo: grey

Todo Form

Functional components (a.k.a stateless components) are good for presentation components because they are simple to manage and reason about when compared with class components.

For that sake, we will create the first presentation component, TodoForm, with a functional component:

const TodoForm = ({addTodo}) => {
  // Input tracker
  let input;

  return (
    <div>
      <input ref={node => {
        input = node;
      }} />
      <button onClick={() => {
        addTodo(input.value);
        input.value = '';
      }}>
        +
      </button>
    </div>
  );
};

Functional components just receive props (which we destructured with ES6) as arguments and return JSX to be rendered. TodoForm has just one prop which is a handler that handles the click event for adding a new todo.

The value of the input is passed to the input member variable using React’s ref.

Todo and Todo List

These components present the list of to-do. TodoList is a ul element that contains a loop of Todo components (made of li elements`):

const Todo = ({todo, remove}) => {
  // Each Todo
  return (<li onClick(remove(todo.id))>{todo.text}</li>);
}

const TodoList = ({todos, remove}) => {
  // Map through the todos
  const todoNode = todos.map((todo) => {
    return (<Todo todo={todo} key={todo.id} remove={remove}/>)
  });
  return (<ul>{todoNode}</ul>);
}

See the Pen AXNJpJ by Chris Nwamba (@christiannwamba) on CodePen.

The remove property is an event handler that will be called when the list item is clicked. The idea is to delete an item when it is clicked. This will be taken care of in the container component.

The only way the remove property can be passed to the Todo component is via its parent (not grand-parent). For this sake, in as much as the container component that will own TodoList should handle item removal, we still have to pass down the handler from grand-parent to grand-child through the parent.

This is a common challenge that you will encounter in a nested component when building React applications. If the nesting is going to be deep, it is advised you use container components to split the hierarchy.

Title

The title component just shows the title of the application:

const Title = () => {
  return (
    <div>
       <div>
          <h1>to-do</h1>
       </div>
    </div>
  );
}

Container Component (Todo App)

This will eventually become the heart of this application by regulating props and managing state among the presentation components. We already have a form and a list that are independent of each other but we need to do some tying together where needed.

// Contaner Component
// Todo Id
window.id = 0;

class TodoApp extends React.Component{
  constructor(props){
    // Pass props to parent class
    super(props);
    // Set initial state
    this.state = {
      data: []
    }
  }
  // Add todo handler
  addTodo(val){
    // Assemble data
    const todo = {text: val, id: window.id++}
    // Update data
    this.state.data.push(todo);
    // Update state
    this.setState({data: this.state.data});
  }
  // Handle remove
  handleRemove(id){
    // Filter all todos except the one to be removed
    const remainder = this.state.data.filter((todo) => {
      if(todo.id !== id) return todo;
    });
    // Update state with filter
    this.setState({data: remainder});
  }

  render(){
    // Render JSX
    return (
      <div>
      <Title />
        <TodoForm addTodo={this.addTodo.bind(this)}/>
        <TodoList
          todos={this.state.data}
          remove={this.handleRemove.bind(this)}
        />
      </div>
    );
  }
}

View CodePen Progress

We first set up the component’s constructor by passing props to the parent class and setting the initial state of our application.

Next, we create handlers for adding and removing todo which the events are fired in TodoForm component and Todo component respectively. setState method is used to update the application state at any point.

As usual, we render the JSX passing in our props which will be received by the child components.

DOM Rendering

We have been rendering our demo components to the browser without discussing how but can be seen in the CodePen samples. React abstracts rendering to a different library called ReactDOM which takes your app’s root component and renders it on a provided DOM using an exposed render method:

ReactDOM.render(<TodoApp />, document.getElementById('container'));

The first argument is the component to be rendered and the second argument is the DOM element to render on.

Working with a Server

We could step up our game by working with an HTTP server rather than just a simple local array. We do not have to bear the weight of jQuery to make HTTP requests, rather we can make use of a smaller library like Axios.

<script src="https://npmcdn.com/axios/dist/axios.min.js"></script>

React lifecycle methods help you hook into React process and perform some actions. An example is doing something once a component is ready. This is done in the componentDidMount lifecycle method. Lifecycle methods are just like normal class methods and cannot be used in a stateless component.

class TodoApp extends React.Component{
  constructor(props){
    // Pass props to parent class
    super(props);
    // Set initial state
    this.state = {
      data: []
    }
    this.apiUrl = 'https://57b1924b46b57d1100a3c3f8.mockapi.io/api/todos'
  }

  // Lifecycle method
  componentDidMount(){
    // Make HTTP reques with Axios
    axios.get(this.apiUrl)
      .then((res) => {
        // Set state with result
        this.setState({data:res.data});
      });
  }
}

Mock API is a good mock backend for building frontend apps that needs to consume an API in the future. We store the API URL provided by Mock API as a class property so it can be accessed by different members of the class just as the componentDidMount lifecycle method is. Once there is a response and the promise resolves, we update the state using:

this.setState()

The add and remove methods now works with the API but also optimized for better user experience. We do not have to reload data when there is new todo, we just push to the existing array. Same with remove:

  // Add todo handler
  addTodo(val){
    // Assemble data
    const todo = {text: val}
    // Update data
    axios.post(this.apiUrl, todo)
       .then((res) => {
          this.state.data.push(res.data);
          this.setState({data: this.state.data});
       });
  }

  // Handle remove
  handleRemove(id){
    // Filter all todos except the one to be removed
    const remainder = this.state.data.filter((todo) => {
      if(todo.id !== id) return todo;
    });
    // Update state with filter
    axios.delete(this.apiUrl+'/'+id)
      .then((res) => {
        this.setState({data: remainder});
      })
  }

Count and Style

We could keep track of the total items in our To-Do with the Title component. This one is easy, place a property on the Title component to store the count and pass down the computed count from TodoApp:

// Title
const Title = ({todoCount}) => {
  return (
    <div>
       <div>
          <h1>to-do ({todoCount})</h1>
       </div>
    </div>
  );
}

// Todo App
class TodoApp extends React.Component{
 //...
 render(){
    // Render JSX
    return (
      <div>
        <Title todoCount={this.state.data.length}/>
        {/* ... */}
      </div>
    );
  }
  //...
}

The app works as expected but is not pretty enough for consumption. Bootstrap can take care of that.

Conclusion

We violated minor best practices for brevity but most importantly, you get the idea of how to build a React app following community-recommended patterns.

As I mentioned earlier, you don’t need to use a state management library in React applications if your application is simpler. Anytime you have doubt if you need them or not, then you don’t need them. (YAGNI).

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Chris Nwamba

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel