This tutorial is out of date and no longer maintained.
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.
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.
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: 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
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.
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:
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.
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.
The title component just shows the title of the application:
const Title = () => {
return (
<div>
<div>
<h1>to-do</h1>
</div>
</div>
);
}
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>
);
}
}
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.
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.
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});
})
}
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.
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.
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!