There are many ways to start a react project. If you are confident with the terminal and npm/yarn, you'll just need to globally install create-react-app
package and then use it to create your react project like so:
create-react-app todo-app
However, for those of you that aren't comfortable with that then you might like to play with codesandbox, simply select react and we are ready to go. That's what I am doing, so follow along there.
In ./index.js
we have the following code, which will not change through out this basic tutorial
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));
We are importing two packages: react
and react-dom
. As it can be clearly seen, render
is one of the methods react-dom
provides. It takes the component App
and renders it in the browser, within a node with the id of root
root
is located inside index.html
Creating the first component
In line 3 we are importing an App
component.
It doesn't exist yet, so lets create ./App.js
and add the following code
import React, { Component } from "react";
import "./style.css";
class App extends Component {
render() {
return (
<div className="wrap">
<h2>Simply todo List</h2>
<form>
<input type="text" />
</form>
<ul>
<li>Get Milk <span>x</span></li>
</ul>
</div>
);
}
}
export default App;
App
is a simple class which extends from react's Component
parent class. Doing so, it gets methods such as render
, used to return JSX, which, judging from the above, it's simple html - but has extended functionality which will see later.
Finally, note, how we can import regular css directly within the component, which ideally gives us the power to fully modularise each component.
Result so far
So far we have created a simple form and an unordered list with one item in it. The end result would be something like this
Working with State
The content returned from our App
component is just static html, not very useful. However, react class components have the ability to create local state, which would make the rendered JSX a lot more dynamic. Let's change the App
component to make use of the local state.
Initial state is set within the class constructor and accessible throughout all the class methods
class App extends Component {
constructor(props){
super(props)
this.state = {
title: 'simple Todo List',
items: [
'Get milk',
'Boil water',
'Bake tea'
]
}
}
...
this.state
takes an object, the contents of which can be anything we want. So we specified a title and an array of item's. The constructor takes props
as an argument and also super(props)
must be called in order for our App
class to inherit data (the props
object) from the parent class, otherwise known as a superclass.
Now let's edit the JSX to instead render the state data where appropriate
render() {
return (
<div className="wrap">
<h2>{this.state.title}</h2>
<form>
<input type="text" />
</form>
<ul>
{
this.state.items.map( (item,id)=>
<li key={id}>{item}</li>)
}
</ul>
</div>
);
}
}
Note how within the curly braces we are able to run pure JavaScript to loop through the items
array in the state.
Two when things there: this.state
get's the state object we specified earlier. And, the use of unique key
inside the li
tag is required by reach every time we iterate through a list, this so that Reach can pinpoint where changes happen and alter the DOM faster.
Modifying the state through user actions
We are now able to manipulate the state based on user input.
The form we render above has two possible actions. We can bind an onChange
event in the input
field and an onSubmit
event on the actual form.
class App extends Component {
....
render() {
return (
<div className="wrap">
..
<form onSubmit={this.submitItem}>
<input type="text" onChange={this.inputChanged} />
</form>
..
</div>
);
}
}
Above we are referring to two methods which don't exist yet. Let's create them
class App extends Component {
...
submitItem(e){
e.preventDefault();
console.log('Form Submited')
}
inputChanged(e){
console.log(e.target.value)
}
...
}
With those in place, we will get the value printed in the console every time we enter something in the input field and get the message Form Submitted
every time we would press enter in the form.
But that's not very useful. We ideally want to change the state when those events are triggered. To change the state we would run setState
method. And it would look something like this.setState()
. However, if we consoled this
in any of the above methods, it would return null
since this
doesn't refer to anything inside inputChanged
or submitItem
. We need to bind these methods to the class. There are two ways to do this. We can bind these methods in the constructor, like so:
constructor(props){
super(props)
this.submitItem = this.submitItem.bind(this)
this.inputChanged = this.inputChanged.bind(this)
...
}
Or we can create the binding as we use the methods.
...
<form onSubmit={this.submitItem.bind(this)}>
<input type="text" onChange={this.inputChanged.bind(this)} />
</form>
...
Both work the same way. Clearly, adding all the binding in the constructor gives us a level of organisation which could be useful in large projects.
Now, this
inside our two methods refers to the component itself, hence, this.state
get's the state object we want to alter.
Lets change the state on submit
Keep in mind the state that we are working with. We have already define it in the constructor:
class App extends Component {
constructor(props){
super(props)
this.state = {
title: 'simple Todo List',
items: [
'Get milk',
'Boil water',
'Bake tea'
]
}
}
...
When the form is submitted, we would like to modify the items
array above. Let's do that, then talk about what's going on
submitItem(e){
e.preventDefault();
let items = this.state.items;
items.push(e.target[0].value)
this.setState({
items
})
}
First line, we just prevent the form from acting in it's default way, in short, we prevent it's default behaviour.
Secondly we get the part of the state we will modify. this.state
gives us the state object. Then on line three we push the form value to the items
array, and finally we reset the state where this.state.items
would include the new content we pushed.
By default the component will re-render causing the render()
method to loop through the new array and display the changes.
Lets change the state on change
Back to the form, we have another method which will be triggered every time users modify the input field
...
<form onSubmit={this.submitItem.bind(this)}>
<input type="text" onChange={this.inputChanged.bind(this)} />
</form>
...
Let's add a property to our state object when that input change happens
inputChanged(e){
this.setState({
ValuePlaceholder: e.target.value
})
}
Which in turn can be accessed inside the submitItem
method, where the way we get the input value can change from
submitItem(e){
...
items.push(e.target[0].value)
...
}
To just grabbing the value from the state
submitItem(e){
...
items.push(this.state.ValuePlaceholder)
...
}
Conclusion
That's the basics of how to create a simple todo app in react.
As I already mentioned I created this project using codesandbox and it was a fantastic experience, The editor is fantastic and the setup is amazingly simple. Especially for beginners that aren't comfortable with the terminal.
It also gave me the ability to push this project into github, so feel free to check that repository but also of course, go checkout the demo, at codesandbox
Top comments (3)
Nice piece. Instead of using the bind can you also try using Public Class Fields. They allow you to add instance properties to the class definition with the assignment operator (=).
Thanks for the suggestion.
Just so that we are on the same page, you are talking about something like this right?
It does work and I like it.
Thanks again
Welcome.