Create Your Own To-Do App With React

Create Your Own To-Do App With React

React is one of the most used JavaScript libraries today. It makes it easier to create interactive UIs. You can use React to design and create both, simple and complex application. You can even use it to create one-page websites if you want. In other words, you can use React to build almost anything. Today, I want to share with you this tutorial on how to create a to-do app with React. Before we start, I will also give you a little bit of theory. I hope you will enjoy this tutorial.

Live Demo

Note: I did my best to explain how this library works and how you can use it. I’m still getting my head around it. So, it is possible that you may find some parts that are not properly explained. Still, I hope this tutorial will help you get into React. If not, let me know on twitter. Tell me which part was the most difficult to understand. I will do my best to fix it. Thank you very much.

Introduction

Let me start this post with something a little bit of theory. What is actually React? In a short, React is a JavaScript library developed by Facebook to create user interfaces. There is already plethora of other JavaScript libraries. So, what makes React different and why it is so popular? React was developed by software engineers at Facebook. It was Jordan Walke who created this library, to be more specific. Engineers at Facebook and Instagram are also the ones who maintain it at this moment. In a fact, the Instagram website runs entirely on React.

React focuses on simple and fast creation of interactive user interfaces. These interfaces are composed of simple and reusable components, properties and states. The first layer, components, are the smallest and fundamental building blocks of React. Without components, there is no UI. The second layer are properties. Properties allow you to pass on values that the children are supposed to take. Properties work in similar way like HTML attributes. These attributes are then available in your component as this.props. You can use this to render dynamic data.

Properties, states and events

Children elements, then, can use this to change their own state. You can also think about properties as an API for specific component. The main goal of properties is passing some message to create communication with other components. Last layer are states. State influences how any given component behaves and renders. Properties and states may look quite similar. The difference between these two is that properties are defined when components are created.

Properties are also visible and you can validate them, if you want. Another layer are states. Unlike properties, states are invisible. You can see states only inside component definitions. This also means that states can’t be validated. Every component in React has both, state and property (props) object. You can use or set different states by using setState method. This will trigger specific update of user interface. It is considered a good practice to use another method getInitialState to set an initial state before introducing any interaction.

React and DOM

The last, but not less important part of React is virtual DOM. Virtual DOM renders components you created as a subtrees of nodes. These nodes are based upon state changes. This virtual DOM is built with the goal to do the smallest amount of DOM manipulation possible. Let me give you some simple example. Let’s assume that you have a representation of some object, a car for example. Your object would have the same properties as the original car. It would have the same color, shape, technical attributes and so on.

Virtual DOM and making changes

Now, imagine you would want to change one of these properties. For example, let’s say you no longer like the color. First, there is a slower and more performance demanding way. You can make the change and then reconstruct the whole object. Sure, if you want to make just one change, this may be enough for you. However, if you want to create something more interactive, this is definitely the way to go. This is where React and virtual DOM comes into play.

React offers another much better way to make changes. When you decide to change something, two things will happen. First, React will run an algorithm to see what has changed. After that, it will take only these changes and implement them without reconstructing the whole object. Let’s get back to your hypothetical car and changing its color. You don’t have to change the color and reconstruct the whole car. You only change the color and all other parts will remain untouched.

Let me give you one more example from web development. Imagine you have a form. This form can change according to the user’s input. For example, it can add some new fields or it can remove them. It can also modify input labels and text. React can make all these changes without reloading the form. You can also squeeze multi-page form on single page. Then, instead of changing the pages you can use React to render new content into the current page. This is also why React is great library for building one-page apps.

React and unidirectional data flow

The last layer of React environment are events. React library comes with built-in event system. This is similar to classic events you know from JavaScript. You can attach these events to components as properties. Then, you can use these events to trigger specific methods. The last thing you should know is that React works in an undirectional flow. This means that data flow throughout the interface in a single direction. This gives you more control over it. How?

All data you use in your interface flow in only one direction. These data flow from parent to child. Thanks to this, you can track the source and also the destination quite easily. This is what makes React different from other architectures such as AngularJS where data flow in both directions. This means that data may come from many parts of the application. Just remember that, in React, the same state produces the same view. The result is much better predictability.

HTML

The majority of code used to create the interface is in JavaScript or JSX. This code is then rendered into container in HTML. This means that our HTML code will be very short. We are going to use a number of external assets thorough this tutorial. When it comes to stylesheets, I included Bootstrap 4 alpha, font awesome and font Roboto. All these assets are hosted on CDN. You can use them as well in your own example. In case of JavaScript, we need only two libraries. The first one is react. The second one is react-dom.

In the code snippet below, basically the most important part is appContainer div. We will use this div as a container to wrap our app. In other words, our app will be rendered inside it. The rest is the usual stuff. Inside head is meta tag for charset, title and meta tag for viewport. Then, we have three stylesheets. In body, there is previously mentioned container and two scripts necessary for running our React app.

Code:

<!DOCTYPE html>
<html>
 <head>
 <meta charset="utf-8" />
 <meta name="viewport" description="width=device-width, initial-scale=1" />
 <title>React Todo App</title>

 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.4/css/bootstrap.min.css">

 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css" />
 
 <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" />
 </head>

 <body>
  <div class="app-container" id="appContainer"></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>

CSS (SCSS)

Before we start with styling, there is one thing. We will be using Sass, the newer syntax, instead of plain CSS in this tutorial. If you are not familiar with Sass, don’t worry. I will include the whole code also in plain CSS in the end.

SCSS Code:

// Remy function
@function remy($value, $base: 16px) {
 @return ($value / $base) * 1rem;
}

// Variables
$color-primary: #039be5;
$color-secondary: #eee;
$radius: 4px;

// Base
html {
 font-size: 16px;
}

body {
 font-size: 100%;
 font-family: 'Roboto', arial, sans-serif;
 background: #f9f9f9;
}

// Custom styles
.app-container {
 margin-top: 2rem;
 margin-right: auto;
 margin-left: auto;
 overflow: hidden;
 max-width: remy(420px);
 background: #fff;
 border-radius: $radius;
 box-shadow: 0 1px 3px rgba(0,0,0,.025), 0 3px 6px rgba(0,0,0,.065);
}

.input-group .form-control:not(:last-child) {
 padding-top: .6rem;
 padding-bottom: .6rem;
 width: 70%;
 border-bottom: 0;
 border-left: 0;
 border-color: $color-secondary;
}

.input-group-addon {
 padding-top: 10.5px;
 padding-bottom: 10.5px;
 min-width: remy(80px);
 width: 30%;
 color: #fff;
 background-color: $color-primary;
 border: 0;
 border-radius: 0;
 
 &:focus {
 outline: 0;
 }
}

.task-list {
 padding: 0;
 margin-bottom: 0;
 margin-left: 0;
 overflow: hidden;
 list-style-type: none;
}

.task-item {
 padding-top: 0.75rem;
 padding-right: 0.75rem;
 padding-bottom: 0.75rem;
 padding-left: 0.75rem;
 width: 100%;
 background: #fff;
 cursor: default;
 border-top: 1px solid $color-secondary;
 
 &:last-of-type {
 border-bottom: 1px solid $color-secondary;
 }
}

.task-remover {
 line-height: 1.45;
 color: #ddd;
 cursor: pointer;
 transition: color .25s ease-in-out;
 
 &:focus,
 &:hover {
 color: #e53935;
 }
}

Complied CSS:

html {
 font-size: 16px;
}

body {
 font-size: 100%;
 font-family: 'Roboto', arial, sans-serif;
 background: #f9f9f9;
}

.app-container {
 margin-top: 2rem;
 margin-right: auto;
 margin-left: auto;
 overflow: hidden;
 max-width: 26.25rem;
 background: #fff;
 border-radius: 4px;
 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.025), 0 3px 6px rgba(0, 0, 0, 0.065);
}

.input-group .form-control:not(:last-child) {
 padding-top: .6rem;
 padding-bottom: .6rem;
 width: 70%;
 border-bottom: 0;
 border-left: 0;
 border-color: #eee;
}

.input-group-addon {
 padding-top: 10.5px;
 padding-bottom: 10.5px;
 min-width: 5rem;
 width: 30%;
 color: #fff;
 background-color: #039be5;
 border: 0;
 border-radius: 0;
}

.input-group-addon:focus {
 outline: 0;
}

.task-list {
 padding: 0;
 margin-bottom: 0;
 margin-left: 0;
 overflow: hidden;
 list-style-type: none;
}

.task-item {
 padding-top: 0.75rem;
 padding-right: 0.75rem;
 padding-bottom: 0.75rem;
 padding-left: 0.75rem;
 width: 100%;
 background: #fff;
 cursor: default;
 border-top: 1px solid #eee;
}

.task-item:last-of-type {
 border-bottom: 1px solid #eee;
}

.task-remover {
 line-height: 1.45;
 color: #ddd;
 cursor: pointer;
 -webkit-transition: color .25s ease-in-out;
 transition: color .25s ease-in-out;
}

.task-remover:focus,
.task-remover:hover {
 color: #e53935;
}

JavaScript

There is one thing I should mention before we start writing any JavaScript code. As a part of learning ES6, I decided to write this tutorial in this new syntax. This means two things. First, if you are not familiar with ES6, you may have some difficulties. I’m kidding. I will also include compiled code example via babel. This code is in old ES5 syntax. If you are not ready to taste the future of JavaScript, you can use this. The second thing is that if you want to run this code on your own, you will need to use some compiler such as babel. Otherwise, the code may not work.

ES6 Code:

// Store app container in variable
const appContainer = document.querySelector('#appContainer');

// Create component for app header composed of input and button
const AppHead = ({addTask}) => {
 // Input Tracker
 let input;
 
 // Return AppHead component
 return (
  <div className='input-group'>
  <input ref={node => {
   input = node;
  }} className='form-control' type='text' />
  <button onClick={() => {
   addTask(input.value);
   input.value = '';
  }} className='input-group-addon'>
   Add task
  </button>
 </div>
 );
};

// Create component for new task composed of list item, text and icon
const Task = ({task, remove}) => {
 // For each task create list item with specific text and icon to remove the task
 return (
  <li className='task-item'>{task.text} <span className='fa fa-trash-o task-remover pull-right' onClick={() => {remove(task.id)}}></span></li>
 );
}

// Create component for list of tasks
const AppList = ({tasks,remove}) => {
 // Create new node for each task
 const taskNode = tasks.map((task) => {
  return (<Task task={task} key={task.id} remove={remove}/>)
 });

 // Return the list component with all tasks
 return (<ul className='task-list'>{taskNode}</ul>);
}

// Create global variable for task id
window.id = 0;

// Create main task app component
class TaskApp extends React.Component {
 constructor(prop) {
  // Provide parent class with prop
  super(prop);

  // Set initial state as empty
  this.state = {
   data: []
  }
 }
 
 // Add task handler
 addTask(val) {
  // Get the data for tasks such as text and id
  const task = {
   text: val,
   id: window.id++
  }
 
  // Update data if input contains some text
  if (val.length > 0) this.state.data.push(task);
 
  // Update state with newest data - append new task
  this.setState({
   data: this.state.data
  });
 }
 
 // Handle remove
 removeTask(id) {
  // Filter all tasks except the one to be removed
  const taskCollection = this.state.data.filter((task) => {
   if (task.id !== id) return task;
  });

  // Update state with filtered results
  this.setState({
   data: taskCollection
  });
 }

 render() {
 // Render whole App component
 // use AppHead and AppList components
 return (
  <div>
   <AppHead addTask={this.addTask.bind(this)}/>
  
   <AppList 
    tasks={this.state.data}
    remove={this.removeTask.bind(this)}
   />
  </div>
 );
 }
}

// Finally, render the whole app
ReactDOM.render(<TaskApp />, appContainer);

ES5 Code straight from babel:

'use strict';
function _classCallCheck(instance, Constructor) {
 if (!(instance instanceof Constructor)) {
  throw new TypeError("Cannot call a class as a function");
 }
}

function _possibleConstructorReturn(self, call) {
 if (!self) {
  throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
 }
  return call && (typeof call === "object" || typeof call === "function") ? call : self;
}

function _inherits(subClass, superClass) {
 if (typeof superClass !== "function" && superClass !== null) {
  throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
 }
 
 subClass.prototype = Object.create(superClass && superClass.prototype, {
  constructor: {
   value: subClass,
   enumerable: false,
   writable: true,
   configurable: true
  }
 });
 
 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

// Store app container in variable
var appContainer = document.querySelector('#appContainer');

// Create component for app header composed of input and button
var AppHead = function AppHead(_ref) {
 var addTask = _ref.addTask;

 // Input Tracker
 var input = undefined;

 // Return AppHead component
 return React.createElement(
  'div', {
   className: 'input-group'
  },
  React.createElement('input', {
   ref: function ref(node) {
   input = node;
  },
  className: 'form-control',
  type: 'text'
  }),
  React.createElement(
   'button', {
    onClick: function onClick() {
     addTask(input.value);
     input.value = '';
   },
    className: 'input-group-addon'
   },
   'Add task'
  )
 );
};

// Create component for new task composed of list item, text and icon
var Task = function Task(_ref2) {
 var task = _ref2.task;
 var remove = _ref2.remove;

 // For each task create list item with specific text and icon to remove the task
 return React.createElement(
  'li', {
   className: 'task-item'
  },
  task.text,
  ' ',
  React.createElement('span', {
   className: 'fa fa-trash-o task-remover pull-right',
   onClick: function onClick() {
    remove(task.id);
   }
  })
 );
};

// Create component for list of tasks
var AppList = function AppList(_ref3) {
 var tasks = _ref3.tasks;
 var remove = _ref3.remove;

 // Create new node for each task
 var taskNode = tasks.map(function(task) {
  return React.createElement(Task, {
   task: task,
   key: task.id,
   remove: remove
  });
 });

 // Return the list component with all tasks
 return React.createElement(
  'ul', {
   className: 'task-list'
  },
  taskNode
 );
};

// Create global variable for task id
window.id = 0;

// Create main task app component
var TaskApp = function(_React$Component) {
 _inherits(TaskApp, _React$Component);

 function TaskApp(prop) {
  _classCallCheck(this, TaskApp);

  var _this = _possibleConstructorReturn(this, _React$Component.call(this, prop));
 
  // Set initial state as empty
  _this.state = {
   data: []
  };
 
  return _this;
 }

 // Add task handler
 TaskApp.prototype.addTask = function addTask(val) {
  // Get the data for tasks such as text and id
  var task = {
   text: val,
   id: window.id++
  };

  // Update data if input contains some text
  if (val.length > 0) this.state.data.push(task);

  // Update state with newest data - append new task
  this.setState({
   data: this.state.data
  });
 };

 // Handle remove
 TaskApp.prototype.removeTask = function removeTask(id) {
  // Filter all tasks except the one to be removed
  var taskCollection = this.state.data.filter(function(task) {
   if (task.id !== id) return task;
  });

  // Update state with filtered results
  this.setState({
   data: taskCollection
  });
 };

 TaskApp.prototype.render = function render() {
  // Render whole App component
  // use AppHead and AppList components
  return React.createElement(
   'div',
   null,
   React.createElement(AppHead, {
    addTask: this.addTask.bind(this)
   }),
   React.createElement(AppList, {
    tasks: this.state.data,
    remove: this.removeTask.bind(this)
   })
  );
 };

 return TaskApp;
}(React.Component);

// Finally, render the whole app
ReactDOM.render(React.createElement(TaskApp, null), appContainer);

Closing thoughts on React

This is it. You’ve created your first app with React library. I hope that this tutorial gave you sufficient amount of information. I also hope that these information were practical enough to get you started.

Do you have any questions, recommendations, thoughts, advice or tip you would like to share with other readers of this blog, and me? Please share it in a comment. You can also send me a mail. I would love to hear from you.

Did you like this article? Please subscribe.

Are you on social media? Let's connect! You can find me on Twitter, GitHub and Dribbble.

Alex Devero

I'm Founder/CEO of DEVERO Corporation. Entrepreneur, designer, developer. My mission and MTP is to accelerate the development of humankind through technology.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.