Skip to main content

React TODO List

Source Code Github

Source Medium

Every web-dev should have one or two of them on Github ~

01 create-react-app

n the following tutorial we’ll use create-react-app to bootstrap our application. It’s an opinionated zero-configuration starter kit for React introduced by Facebook in 2016. We can install create-react-app by using npm:

npm install -g create-react-app

Having completed the installation successfully we're able to use create-react-app to initiate a new React project:

create-react-app obligatory-react-todo-list-2017

This creates a new initial React project in the folder obligatory-react-todo-list-2017. Dependencies are installed automatically. Change into the folder and start the app with npm start on localhost:3000.

02 Set Initial State

Now open the file ./src/App.js inside your code editor and add some Todo's right below the import statements (delete the <App /> component, that was created below):

// add initial data model array
var todos = [
{
todoTitle: 'Do some coding',
todoResponsible: 'Me',
todoDescription: 'Todo description',
todoPriority: 'medium'
},
{
todoTitle: 'Drink Coffee',
todoResponsible: 'Me',
todoDescription: 'Todo description',
todoPriority: 'high'
},
{
todoTitle: 'Do some more coding',
todoResponsible: 'Me',
todoDescription: 'Todo description',
todoPriority: 'low'
}
]

Now add the todos array to the state of app component. This is done by introducing a class constructor where we can set the initial component state like you can see in the following:

class App extends Component {

// set initial component state to todos array
constructor(props) {
super(props);
this.state = {
todos
};
}
[...]
}

03 JSX, Font-Awesome and Bootstrap

We want to use Bootstrap CSS for our rendered app, which we include via CDN links (see getbootstrap.com) inside the public/index.html page.

<head>
<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css' integrity='sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M' crossorigin='anonymous'>
</head>

<body>
<script src='https://code.jquery.com/jquery-3.2.1.slim.min.js' integrity='sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN' crossorigin='anonymous'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js' integrity='sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4' crossorigin='anonymous'></script>
<script src='https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js' integrity='sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1' crossorigin='anonymous'></script>
</body>

Now we can use bootstrap classNames directly inside the render statement of our <App /> component in src/app.js.

For Font Awesome, we download the zip archive from fontawesome.io and copy only the *.css and web-font files to src/fonts. All web-font files are referenced inside font-awesome.css - the relative path has to be changed from ../fonts/[filename] to ./[filename]! Font-Awesome can now be added to our JSX code inside the render method - but remember to change class to className!

<i className='fa fa-user-circle-o' aria-hidden='true'></i>

Our render function now looks like this, giving us a beautiful Bootstrap UI with some Font-Awesome goodness:

render() {
return (
<div className='container'>

<nav className='navbar fixed-top navbar-dark bg-dark'>
<img src={logo} className='App-logo' alt='logo' />
<h4 className='navbar-brand'>
Todo Count: <span className='badge badge-pill badge-primary'>{this.state.todos.length}</span>
</h4>
</nav>

<div className='row mt-5'>
<div className='col'>
<ul className='list-group'>
{ this.state.todos.map((todo, index) =>
<li className='list-group-item' key={index}>
<h4 className='list-group-item-heading'>{todo.todoTitle} <small><span className='badge badge-secondary'>{todo.todoPriority}</span></small></h4>
<p><i className='fa fa-user-circle-o' aria-hidden='true'></i> {todo.todoResponsible}</p>
<p className='text-justify'>{todo.todoDescription}</p>
<button className='btn btn-danger btn-sm float-right' onClick={this.handleRemoveTodo.bind(this, index)}><span><i className='fa fa-trash-o' aria-hidden='true'></i></span>&nbsp;&nbsp; Delete</button>
</li>
)}
</ul>
</div>
</div>
</div>
);
}

The app should automatically reload inside of your browser and display the basic bootstrap layout of our app, using the data from the todos-array:

04 Add Remove TODO function

Now we want to add a Delete function to the Delete button we added above. We do this, by adding an onClick event handler to the button:

<button className='btn btn-danger btn-sm float-right' onClick={this.handleRemoveTodo.bind(this, index)}>
<span>
<i className='fa fa-trash-o' aria-hidden='true'></i>
</span>&nbsp;&nbsp; Delete</button>

Then we have to define the handleRemoveTodo function inside src/App.js above the render method of <App />:

handleRemoveTodo(index) {
this.setState({
todos: this.state.todos.filter(function(e, i) {
return i !== index;
})
})
}

05 Add a Add TODO function

For now we just want to add a method to add TODOs to our list - so we create a new function below <App /> called <TodoInput />

class TodoInput extends Component {
constructor(props) {
super(props);

this.state = {
todoTitle: '',
todoResponsible: '',
todoDescription: '',
todoPriority: 'lowest'
}
}

render() {
return (
<div className='col'>
<br/><br/><br/>
<h4>Add New Todo</h4><br/>
<form onSubmit={this.handleSubmit}>
<div className='form-group'>
<input name='todoTitle'
type='text'
className='form-control'
id='inputTodoTitle'
value={this.state.todoTitle}
onChange={this.handleInputChange}
aria-describedby='Todo Title'
placeholder='Enter Title'></input>
</div>
<div className='form-group'>
<label htmlFor='inputTodoPriority' className='control-label text-muted'><small>Priority</small></label>
<select name='todoPriority'
type='text'
className='form-control'
id='inputTodoPriority'
value={this.state.todoPriority}
onChange={this.handleInputChange}
aria-describedby='Todo Priority'>
<option>lowest</option>
<option>low</option>
<option>medium</option>
<option>high</option>
<option>emergency</option>
</select><br/>
</div>
<div className='form-group'>
<label htmlFor='inputTodoDescription' className='control-label text-muted'><small>Description</small></label>
<textarea name='todoDescription'
type='text'
className='form-control'
id='inputTodoDescription'
value={this.state.todoDescription}
onChange={this.handleInputChange}
aria-describedby='Todo Description'></textarea>
</div>
<div className='form-group'>
<label htmlFor='inputTodoResponsible' className='control-label text-muted'><small>Responsible</small></label>
<select name='todoResponsible'
type='text'
className='form-control'
id='inputTodoResponsible'
value={this.state.todoResponsible}
onChange={this.handleInputChange}
aria-describedby='Todo Responsible'>
<option>someone else</option>
<option>Mike Polinowski</option>
<option>Micro Aggressions</option>
<option>Vladimir Putin</option>
<option>Climate Change</option>
</select><br/>
</div>
<div className='form-group'>
<button type='submit' className='btn btn-primary float-right'>Add Todo</button>
</div>
</form>
</div>
)
}
}

Now we have to define handleInputChange and handleSubmit above the render method:

handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;

this.setState({
[name]: value
})
}

handleSubmit(event) {
event.preventDefault();
this.props.onAddTodo(this.state);
this.setState({
todoTitle: '',
todoResponsible: '',
todoDescription: '',
todoPriority: 'lowest'
})
}

And bind this to those functions inside the constructor - so we get access to the state of todos:

constructor(props) {
super(props);

this.state = {
todoTitle: '',
todoResponsible: '',
todoDescription: '',
todoPriority: 'lowest'
}

this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

Now we just need to pass down the state of todo via props in <App />:

<TodoInput onAddTodo={this.handleAddTodo}/>

Define the handleAddTodo method above the render call:

handleAddTodo(todo) {
this.setState({todos: [...this.state.todos, todo]});

And bind this inside the constructor:

constructor(props) {
super(props);
this.state = {
todos
};
this.handleAddTodo = this.handleAddTodo.bind(this);
}