上QQ阅读APP看书,第一时间看更新
Todo list – implementing ComponentWillMount
In this recipe, you will learn about the lifecycle methods in React. We will see how the information flows through the methods since the component is pre-mounted, mounted, and unmounted. The Todo list that we will develop in this recipe will look like this:
- For this Todo list, we need to create a new folder called Todo into our components directory, and you also need to create files called Todo.js and Todo.css. This is the skeleton of the Todo component:
import React, { Component } from 'react';
import './Todo.css';
class Todo extends Component {
constructor() {
super();
}
componentWillMount() {
}
render() {
return (
<div className="Todo">
<h1>New Task:</h1>
</div>
);
}
}
export default Todo;
File: src/components/Todo/Todo.js
- Constructor: A constructor is a unique method that is executed before the object is initialized. A constructor can use the super keyword to call the constructor of the super class (parent class). This method is used to initialize our local state or to bind our methods. For the Todo list, we need to initialize the local state in the constructor with some values in the task and items array:
constructor() {
super();
// Initial state...
this.state = {
task: '',
items: []
};
}
- The componentWillMount method is executed once before the component is mounted. In this case, before our component is mounted we need to update our items state with the default tasks:
componentWillMount() {
// Setting default tasks...
this.setState({
items: [
{
id: uuidv4(),
task: 'Pay the rent',
completed: false
},
{
id: uuidv4(),
task: 'Go to the gym',
completed: false
},
{
id: uuidv4(),
task: 'Do my homework',
completed: false
}
]
});
}
- We are using uuidv4 to generate random IDs. To install this package, you need to run the following command:
npm install uuid
- And then you need to import it like this:
import uuidv4 from 'uuid/v4';
- After we defined our default tasks, let's see how we need to render the Todo list:
render() {
return (
<div className="Todo">
<h1>New Task:</h1>
<form onSubmit={this.handleOnSubmit}>
<input
value={this.state.task}
onChange={this.handleOnChange}
/>
</form>
<List
items={this.state.items}
markAsCompleted={this.markAsCompleted}
removeTask={this.removeTask}
/>
</div>
);
}
- Our JSX is divided into two parts. The first one is a form with an input that is connected to our local state (this.state.task), and we will save the task when the user submits the form (onSubmit). The second part is the component list where we are going to display our Todo list (or tasks list), passing the items array and the markAsCompleted (to mark a task as a completed) and removeTask (to remove the task from the list) functions.
- The handleOnChange method is for connecting our input value with our state task:
handleOnChange = e => {
const { target: { value } } = e;
// Updating our task state with the input value...
this.setState({
task: value
});
}
- The handleOnSubmit method is for updating the items state and pushing the new task to the array:
handleOnSubmit = e => {
// Prevent default to avoid the actual form submit...
e.preventDefault();
// Once is submited we reset the task value and we push
// the new task to the items array.
if (this.state.task.trim() !== '') {
this.setState({
task: '',
items: [
...this.state.items,
{
id: uuidv4(),
task: this.state.task,
complete: false
}
]
});
}
}
- The markAsCompleted function is going to be called from our List component and needs to receive the id of the task we want to mark as completed. With this, we can find the specific task in our items array, modify the node as completed, and then update the local state:
markAsCompleted = id => {
// Finding the task by id...
const foundTask = this.state.items.find(
task => task.id === id
);
// Updating the completed status...
foundTask.completed = true;
// Updating the state with the new updated task...
this.setState({
items: [
...this.state.items,
...foundTask
]
});
}
- The removeTask function is also being called from the List component, and like markAsCompleted, we need to receive the id to remove the specific task:
removeTask = id => {
// Filtering the tasks by removing the specific task id...
const filteredTasks = this.state.items.filter(
task => task.id !== id
);
// Updating items state...
this.setState({
items: filteredTasks
});
}
- Let's put all the pieces together. Our Todo component should look like this:
import React, { Component } from 'react';
import uuidv4 from 'uuid/v4';
import List from './List';
import './Todo.css';
class Todo extends Component {
constructor() {
super();
// Initial state...
this.state = {
task: '',
items: []
};
}
componentWillMount() {
// Setting default tasks...
this.setState({
items: [
{
id: uuidv4(),
task: 'Pay the rent',
completed: false
},
{
id: uuidv4(),
task: 'Go to the gym',
completed: false
},
{
id: uuidv4(),
task: 'Do my homework',
completed: false
}
]
});
}
handleOnChange = e => {
const { target: { value } } = e;
// Updating our task state with the input value...
this.setState({
task: value
});
}
handleOnSubmit = e => {
// Prevent default to avoid the actual form submit...
e.preventDefault();
// Once is submitted we reset the task value and
// we push the new task to the items array.
if (this.state.task.trim() !== '') {
this.setState({
task: '',
items: [
...this.state.items,
{
id: uuidv4(),
task: this.state.task,
complete: false
}
]
});
}
}
markAsCompleted = id => {
// Finding the task by id...
const foundTask = this.state.items.find(
task => task.id === id
);
// Updating the completed status...
foundTask.completed = true;
// Updating the state with the new updated task...
this.setState({
items: [
...this.state.items,
...foundTask
]
});
}
removeTask = id => {
// Filtering the tasks by removing the specific task id...
const filteredTasks=this.state.items.filter(
task => task.id !== id
);
// Updating items state...
this.setState({
items: filteredTasks
});
}
render() {
return (
<div className="Todo">
<h1>New Task:</h1>
<form onSubmit={this.handleOnSubmit}>
<input
value={this.state.task}
onChange={this.handleOnChange}
/>
</form>
<List
items={this.state.items}
markAsCompleted={this.markAsCompleted}
removeTask={this.removeTask}
/>
</div>
);
}
}
export default Todo;
File: src/components/Todo/Todo.js
- Now that we have completed our Todo component, let's see what our List component looks like:
import React from 'react';
const List = props => (
<ul>
{props.items.map((item, key) => (
<li
key={key}
className={`${item.completed ? 'completed' : 'pending'}`}
>
{/*
* If the task is completed we assign the
* .completed class otherwise .pending
*/}
{item.task}
<div className="actions">
{/*
* Using a callback on the onClick we call our
* markAsCompleted function
*/}
<span
className={item.completed ? 'hide' : 'done'}
onClick={() => props.markAsCompleted(item.id)}
>
<i className="fa fa-check"></i>
</span>
{/*
* Using a callback on the onClick we call
* our removeTask function
*/}
<span
className="trash"
onClick={() => props.removeTask(item.id)}
>
<i className="fa fa-trash"></i>
</span>
</div>
</li>
))}
</ul>
);
export default List;
File: src/components/Todo/List.js
- Every time we use a .map function to render multiple React elements from an array, we must add the key prop to each item we created. Otherwise, we will get a React warning like this:
- You have probably noticed that we also included some Font Awesome icons, and to make it work we need to add the Font Awesome CDN into the main index.html file:
<head>
<title>React App</title>
<link
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
rel="stylesheet"
/>
</head>
File: public/index.html
- The last part is the CSS for the Todo list (you're free to change the styles if you prefer):
.Todo {
background-color: #f5f5f5;
border-radius: 4px;
border: 1px solid #e3e3e3;
box-shadow: inset 0 1px 1px rgba(0,0,0,.05);
margin: 50px auto;
min-height: 20px;
padding: 20px;
text-align: left;
width: 70%;
}
.Todo ul {
margin: 20px 0px;
padding: 0;
list-style: none;
}
.Todo ul li {
background-color: #fff;
border: 1px solid #ddd;
display: flex;
justify-content: space-between;
margin-bottom: -1px;
padding: 10px 15px;
}
.Todo ul li .hide {
visibility: hidden;
}
.Todo ul li.completed {
background-color: #dff0d8;
}
.Todo ul li .actions {
display: flex;
justify-content: space-between;
width: 40px;
}
.Todo ul li span {
cursor: pointer;
}
.Todo ul li .done {
color: #79c41d;
display: block;
}
.Todo ul li .trash {
color: #c41d1d;
display: block;
}
.Todo form input {
background-color: #fff;
border-radius: 4px;
border: 1px solid #ccc;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
color: #555;
font-size: 14px;
height: 34px;
line-height: 34px;
padding: 6px 12px;
width: 40%;
}
File: src/components/Todo/Todo.css
- Don't forget to import the Todo component into your App component. Otherwise, the component won't render:
import React, { Component } from 'react';
import Todo from './Todo/Todo';
import Header from '../shared/components/layout/Header';
import Content from '../shared/components/layout/Content';
import Footer from '../shared/components/layout/Footer';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<Header title="Todo List" />
<Content>
<Todo />
</Content>
<Footer />
</div>
);
}
}
export default App;
File: src/components/App.js
- If you followed all the instructions correctly you should see the Todo List like this:
- The initial state with default tasks:
- Adding a new task:
- Write the task title and then press Enter:
- Mark a task as complete:
- Removing a task:
I challenge you to save the tasks using localStorage instead of defining the default tasks with componentWillMount.