React Hooks for beginners (useState)
React 16.8 was released with hooks, which are an additional feature that allows us to use state and other react features without writing a class.
If you have worked with react before, you should know that if you want to use state in your component, you have to use classes. Well that’s not necessarily in the case anymore. Now you can have state in your functional components using something called useState.
Our final product will look like the image below.
You can follow through the tutorial by watching the video below or just continue reading this post.
Let’s get started – we are going to create a new react application using create react app cli. Let’s head over to create react app GitHub and copy the commands we need from there.
Let’s open a terminal window and create a new app called my-todo-list. Open the generated react project in a code editor of your choice. I am going to be using visual studio code. Let’s open up our terminal and run yarn start
Great, our app is running. Let’s start coding. Let’s write all of our code in App.js. Start by deleting all the code in the file and add the two lines below.
import React, { useState } from 'react';
import './App.css';
First, we import react and useState hook. The useState hook will make it possible to have state in our functions. In previous react versions, if you want to use state in your react component you had to use classes with a constructor. Therefore, the components we are going to write here are going to be purely functional. The second line imports our default css that I have already written below.
@import url('https://fonts.googleapis.com/css?family=Roboto:400,700');
body{
background-color: #5856d6;
color: #fff;
font-family: 'Roboto', sans-serif;
font-size: 20px;
}
.app {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 15px;
width: 500px;
margin: auto;
}
form{
width: 100%;
}
form div{
width: 100%;
display: flex;
flex-direction: row;
}
button{
font-weight: bold;
font-size: 12px;
border: none;
}
form button {
background-color:#656565;
color: #fff;
}
input{
width: 100%;
padding: 12px 20px;
box-sizing: border-box;
}
.list-item{
width: 100%;
margin-top: 20px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.status-btn{
height: 40px;
background-color: #4cd964;
color: #fff;
}
.done{
background-color: #ff3b30;
}
.strike-through{
color: #ff3b30;
text-decoration: line-through;
}
Now let’s code our main component inside App.js.
function App (){
const [todos, setTodos] = useState([{
text:"buy groceries",
isDone: false
},{
text:"Go to the gym",
isDone: false
},{
text:"Pay credit card bill",
isDone: false
}]);
return (
<div className="app">
<h1>Todo list</h1>
</div>
);
}
On the second line we are destructuring todos and setTodos from useState. The first value “ todos ” will be our data. This is almost similar to this.state.todos. The second value “ setTodos” is a method which will use to manipulate “todos”. This is almost similar to this.setState. Note that you can name these two anything you want as you will see later on. The array of objects inside useState will be initial value of todos.
Now lets expand our code and render the the todo list
function App (){
const [todos, setTodos] = useState([{
text:"buy groceries",
isDone: false
},{
text:"Go to the gym",
isDone: false
},{
text:"Pay credit card bill",
isDone: false
}]);
const addTodo = (text)=>{
setTodos([...todos, {text}]);
}
const toggleTodoStatus = (payload)=>{
const { status, index } = payload;
const myNewTodos = [...todos];
myNewTodos[index].isDone = status;
setTodos(myNewTodos);
}
return (
<div className="app">
<h1>Todo list</h1>
{
todos.map((todo, index)=>{
const { text, isDone } = todo;
const btnText = isDone ? "Undo" : "Done";
return(
<div key={index} className="list-item">
<span className={isDone ? "strike-through" : ""}>
{text}
</span>
<button
className={`status-btn ${isDone ? "done" : ""}`}
onClick={
()=>toggleTodoStatus({status:!isDone, index})
}
>
{btnText}
</button>
</div>
)
})
}
</div>
);
}
Using todos.map
in the code above we loop through the todos array. Using const { text, isDone } = todo;
we de-structure the text and the boolean is done from the todo object. isDone will be used to highlight whether a todo has been completed or not. On the next line we create a variable called btnText which the value is dependent on the todo items completion state. The value is used on line 44 {btnText}
as the button value. To mark our todos as completed we strike through the text with a line as show below.
The function toggleTodoStatus({status:!isDone, index}) takes an object with two keys, status and index. The status value will the opposite of whatever the current state of isDone boolean hence the use of “!” logical not operator in javascript. The index is the position of the todo object in the array which we will use to update the state. Let’s look at the function declaration below.
const toggleTodoStatus = (payload)=>{
const { status, index } = payload;
const myNewTodos = [...todos];
myNewTodos[index].isDone = status;
setTodos(myNewTodos);
}
The function uses setTodos to update the value of the todos item just like this.setState.
To add to the list of todos, we create a form component called AddTodoForm.
function AddTodoForm ({addTodo}){
const [text, setText] = useState("");
const submitTodo = (ev)=>{
ev.preventDefault();
if(text){
addTodo(text);
setText("");
}else{
alert("A value is required!");
}
}
return(
<form onSubmit={submitTodo}>
<label>Add a todo</label>
<div>
<input
className="todo-input"
placeholder="Start typing"
onChange={(ev)=>{
setText(ev.target.value);
}}
value={text}
/>
<button>Submit</button>
</div>
</form>
)
}
As you can see on the second line, we destructure again from useState like this const [text, setText] = useState("");
naming the values anything we want. The variable text will hold the text in put value while setText is used to update it as the user types.
Finally let’s create a function inside our App component above the return method. The function will update the todos array when the user clicks the submit button in our AddTodoForm component. We will pass this function to our AddTodoForm as a prop.
const addTodo = (text)=>{
setTodos([...todos, {text}]);
}
return (
<div className="app">
<h1>Todo list</h1>
<AddTodoForm addTodo={addTodo}/>
...
Our final code will look like below
import React, { useState } from 'react';
import './App.css';
function AddTodoForm ({addTodo}){
const [text, setText] = useState("");
const submitTodo = (ev)=>{
ev.preventDefault();
if(text){
addTodo(text);
setText("");
}else{
alert("A value is required!");
}
}
return(
<form onSubmit={submitTodo}>
<label>Add a todo</label>
<div>
<input
className="todo-input"
placeholder="Start typing"
onChange={(ev)=>{
setText(ev.target.value);
}}
value={text}
/>
<button>Submit</button>
</div>
</form>
)
}
function App (){
const [todos, setTodos] = useState([{
text:"buy groceries",
isDone: false
},{
text:"Go to the gym",
isDone: false
},{
text:"Pay credit card bill",
isDone: false
}]);
const toggleTodoStatus = (payload)=>{
const { status, index } = payload;
const myNewTodos = [...todos];
myNewTodos[index].isDone = status;
setTodos(myNewTodos);
}
const addTodo = (text)=>{
setTodos([...todos, {text}]);
}
return (
<div className="app">
<h1>Todo list</h1>
<AddTodoForm addTodo={addTodo}/>
{
todos.map((todo, index)=>{
const { text, isDone } = todo;
const btnText = isDone ? "Undo" : "Done";
return(
<div key={index} className="list-item">
<span className={isDone ? "strike-through" : ""}>
{text}
</span>
<button
className={`status-btn ${isDone ? "done" : ""}`}
onClick={()=>toggleTodoStatus({status:!isDone, index})}
>
{btnText}
</button>
</div>
)
})
}
</div>
);
}
export default App;
where is the video?
You can find it here: https://www.youtube.com/watch?v=MlrF-daaOT8