Create a Simple To-Do App With React

Learn the React basics by building a simple to-do application.

You could be wondering what is so special about React; What we will do is pick up from a previous post about React components and put to practice the theories we discussed following community best practices as always.

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 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 component. 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 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 much nested children and less complex. A To-Do is 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 becomes 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 a 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 & 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 it's to the Todo component is via it's 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 on 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>
    );
  }
}

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

We first setup 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 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 a HTTP server rather than just a simple local array. We do not have to bear the weight of jQuery to make HTTP request, 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 not pretty enough for consumption. Bootstrap can take care of that. The following CodePen demo shows some updates made to change the look of the app:

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

What Next

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).

On the other hand, expect an article on Redux from Scotch soon.

Chris Nwamba

Passion for instructing computers and understanding its language. Would love to remain a software engineer in my next life.