Intro to React Hooks | Todo App
Tags: javascript, react, redux, tutorial
Published: 2019-09-23 16:53:36

React is one of the most popular and growing UI libraries today. With every new release, there are new features and deprecations along with it. Let's talk about one of the newest features of React, known as Hooks.

## Why React Hooks?

<span class="u-text--highlight">**Hooks**</span> are a new addition to React 16.8 to provide state management and side-effects in function components.

1) Earlier, only class components were used for local state management and lifecycle methods. These lifecycle methods have been essential for introducing side-effects, such as data fetching, listeners and many more. This lead to a lot of refactoring from <span class="u-text--highlight">**functional stateless components**</span> to <span class="u-text--highlight">**stateful class components**</span> whenever a functional component needed to use state or lifecycle methods. With Hooks, we can use features like _state_ and _effects_ without actually any component transformation. We will talk more about <span class="u-text--highlight">**useState**</span> and <span class="u-text--highlight">**useEffect**</span> while building the app later in the post.

```jsx
import React, { useState, useEffect } from 'react';

function Counter() {
	// Using state in a functional component
	const [count, setCount] = useState(0);

	// Using effects in a functional component
	useEffect(() => {
		document.title = `Counter: ${count}`;
	}, [count]);

	render(
		<div>
			<p>You have clicked {count} times</p>
			<button onClick={() => setCount(count + 1)}>Increment</button>
		</div>
	);
}
```

2) Reusable stateful behavior between React Components is a bit tricky. Though, it can be done using patterns like <a href="https://reactjs.org/docs/render-props.html" target="_blank">render props</a> and <a href="https://reactjs.org/docs/higher-order-components.html" target="_blank">higher order components</a>. Using such patterns, components have to be restructured which makes code harder to understand and maintain. With Hooks, stateful logic can be extracted from the components into their own custom hooks which allows them to be tested independently and can be reused.

## Let's start building!

We are going to build a simple Todo App in this blog post. Demo for the same can be seen <a href="https://codesandbox.io/s/71j9q45y11" target="_blank">here</a> and Github repo <a href="https://github.com/yomeshgupta/react-hooks-todo-app" target="_blank">here</a>.

```jsx
import React, { useState } from 'react';

function Todos() {
	// Creating a todos state variable
	const [todos, setTodos] = useState({
		1552406885681: {
			todo: 'Complete this blog post',
			isComplete: false
		}
	});

	// Rendering the todos
	return <div className="wrapper">
		<ul id="todos">
			{Object.entries(todos).map(([key, value]) => <li key={key}>{value.todo}</li>);}
		</ul>
	</div>;
}
```

### Defining state with useState()

Like mentioned earlier, now we can do state management in functional components and to do so React provides us with a hook called <span class="u-text--highlight">**useState**</span>.

1) It takes an initial state. Unlike class component's state, useState's initial state need not be an object. It can be a string, boolean, object or any other possible value in JavaScript.

```jsx
const [count, setCount] = useState(0); // number
const [name, setName] = useState('Yomesh'); // string
const [fetched, setFetched] = useState(false); // boolean
const [todos, setTodos] = useState({}); // object
```

2) It declares a "state variable" whose value presists between the function calls. It provides exact same capabilities as <span class="inline-code">`this.state`</span>.

3) It returns a pair of values: the current state and a function that updates it. We get these return values via array destructing <span class="inline-code">`const [todos, setTodos] = useState({});`</span>

In the above code example, we created a state variable called <span class="inline-code">`todos`</span> with default value as our current todos.

### Fetching Todos

Earlier, we have provided a default value to our todos but what if we have to fetch those todos from a remote API? In such cases, we need something like <span class="inline-code">`componentDidMount`</span> for data fetching like we used to do in class components.

React provides us with a hook called <span class="inline-code">`useEffect`</span> which can be used directly into a component and provides a way to mimick these lifecycle methods and go beyond that. When we talk about effects, we are basically referring to things like data fetching, updates to the DOM, event listeners, and likes. Let's see this in action step by step.

```jsx
import React, { useState, useEffect } from 'react';

const TODOS = {
	1552406885681: {
		todo: 'Complete this blog post',
		isComplete: false
	}
};

function Todos() {
	// Creating a todos state variable
	const [todos, setTodos] = useState({});

	// Setting up an effect
	useEffect(function() {
		// fetch(REMOTE_URL).then(response => setTodos(response));

		// mocking API call
		return new Promise((resolve, reject) => {
			setTimeout(() => resolve(TODOS), 2000);
		}).then(response => {
			// Updating state variable
			setTodos(response);
		});
	});

	// Rendering the todos
	return (
		<div className="wrapper">
			<ul id="todos">
				{Object.keys(todos).map(key => {
					const value = todos[key];
					return <li key={key}>{value.todo}</li>;
				})}
			</ul>
		</div>
	);
}
```

<span class="inline-code">`useEffect`</span> always run after the render. So, they are non-blocking in nature. Consider them like giving React an instruction and executing it after the DOM has rendered. It takes two arguments: a function which will be executed after the render and a dependency array (More on this below).

In the above code snippet:

1) We are setting up an effect which is used to fetch data from an API (mocking). So, initially, our todos will be empty and we won't be seeing anything. After the inital render, <span class="inline-code">`useEffect`</span> will execute and data is being fetched.

2) An effect hook should return nothing or a cleanup function. That's why you may see the following error in your developer console log - <span class="inline-code">`Warning: An Effect function must not return anything besides a function, which is used for clean-up. It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, you may write an async function separately and then call it from inside the effect`</span>. We can fix this via

```jsx
	...
	// Setting up an effect
	useEffect(function() {
		function fetchData() {
			// fetch(REMOTE_URL).then(response => setTodos(response));

			// mocking API call
			new Promise((resolve, reject) => {
				setTimeout(() => resolve(TODOS), 2000);
			}).then(response => {
				// Updating state variable
				setTodos(response);
			});
		}

		fetchData();
	});
	...
}
```

3) This is all good and fine but if you execute the code so far, you will see that <span class="inline-code">`fetchData`</span> will be called after every render as <span class="inline-code">`useEffect`</span> executes after every render too! Check out this <a href="https://codesandbox.io/s/xpjrkj2wxo" target="_blank">sandbox</a> to see what I mean.

To avoid this infinite loop and for optimisation, <span class="inline-code">`useEffect`</span> takes a second argument which is called a <span class="inline-code">`dependency array`</span>. In this array we can mention all the variables on whose value change -- execution of <span class="inline-code">`useEffect`</span> depends. Suppose, we have a use-case where we need to display a list of items, fetched from a remote API, based on an input query. In this case, input query would be part of the dependency array. Check out this <a href="https://codesandbox.io/s/l0n6qn3x7" target="_blank">sandbox</a> for a live example.

But in our app, we only need to fetch data once after the initial load. We can do that by providing an empty array as the second argument to <span class="inline-code">`useEffect`</span>. By doing so, our effect will only run once after the initial render, acting just like <span class="inline-code">`componentDidMount`</span> here.

```jsx
...
useEffect(function() {
	{
		/* some processing */
	}
}, []); // acts like componentDidMount
...
```

4) Now, the data fetching part is done. Focus on the code below

```jsx
.then(response => setTodos(response));
```

As we discussed earlier, <span class="inline-code">`const [todos, setTodos] = useState({})`</span> provides us two return values. Second value is a function which allows us to update the state variable. Here, it is same as doing <span class="inline-code">`this.setState({ todos: response })`</span>
in a class component.

### Adding Loader and Empty State

We will add a <span class="inline-code">`Loader`</span> and <span class="inline-code">`Empty`</span> component. Initially, there would be no todos so <span class="inline-code">`Loader`</span> will be displayed and if the fetch returns no result then <span class="inline-code">`Empty`</span> state will be displayed.

```jsx
...
function Loader() {
	return <div id="loader">Loading...</div>;
}

function Empty() {
	return <div id="empty">No todos found...</div>;
}

function Todos() {
	const [fetched, setFetched] = useState(false);
	const [todos, setTodos] = useState({});
	const keys = Object.keys(todos);

	useEffect(function() {
		function fetchData() {
			new Promise((resolve, reject) => {
				setTimeout(() => resolve(TODOS), 2000);
			}).then(response => {
				setFetched(true);
				setTodos(response);
			});
		}

		fetchData();
	}, []);

	function renderContent() {
		if (!fetched) {
			return <Loader />;
		} else if (!keys.length) {
			return <Empty />;
		}
		return (
			<ul id="todos">
				{keys.map(key => {
					const value = todos[key];
					return <li key={key}>{value.todo}</li>;
				})}
			</ul>
		);
	}

	return <div className="wrapper">{renderContent()}</div>;
}
```

### Refactoring...

So far so good but we can take it up a notch. People coming from the realm of <span class="inline-code">`Redux`</span> will enjoy it.

#### Welcome useReducer!

It is an alternative to <span class="inline-code">`useState`</span>. It accepts three arguments -- <span class="inline-code">`reducer`</span>, <span class="inline-code">`initialState`</span>, <span class="inline-code">`init function`</span> and returns current <span class="inline-code">`state`</span> and <span class="inline-code">`dispatch`</span> function to update that state.

As per React documentation,

> useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.

```jsx
const [state, dispatch] = useReducer(reducer, initialState, init);
```

Let's refactor some of our code now.

```jsx
import React, { useEffect, useReducer } from 'react';

const TODOS = {
	1552406885681: {
		todo: 'Complete this blog post',
		isComplete: false
	}
};

const initialState = {
	fetched: false,
	todos: {}
};

function reducer(state, action) {
	switch (action.type) {
		case 'REPLACE_TODOS':
			return { ...state, fetched: true, todos: action.payload };
		default:
			return state;
	}
}
...
function Todos() {
	const [state, dispatch] = useReducer(reducer, initialState);
	const { fetched, todos } = state;
	const keys = Object.keys(todos);

	useEffect(function() {
		function fetchData() {
			new Promise((resolve, reject) => {
				setTimeout(() => resolve(TODOS), 2000);
			}).then(response => {
				dispatch({
					type: 'REPLACE_TODOS',
					payload: response
				});
			});
		}

		fetchData();
	}, []);
	...
}
```

We can use <span class="inline-code">`dispatch`</span> deep inside component hierarchy and update our state, just like good old plain <span class="inline-code">`Redux Actions`</span>!

### Let's Save, Complete and Delete

In this section, we are going to add the following functionalities

#### <span class="u-text--highlight">**Saving a new todo**</span>

Here, we declare a new state variable <span class="inline-code">`task`</span> and will add a form. We are going to capture the new todo in the state variable and add it to the list via dispatching a new action when the form submits.

```jsx
...
const [task, setTask] = useState('');

function reducer(state, action) {
	switch (action.type) {
		...
		case 'ADD_TODO':
			return {
				...state,
				todos: {
					...state.todos,
					...action.payload
				}
			}
		...
	}
}
...
function saveHandler(e) {
	e.preventDefault();
	dispatch({
		type: 'ADD_TODO',
		payload: {
			[+new Date()]: {
				todo: task,
				isComplete: false
			}
		}
	});
	setTask('');
}

return (
	<div className="wrapper">
		<form method="POST" onSubmit={saveHandler}>
			<input type="text" onChange={e => setTask(e.target.value)} value={task} placeholder="What needs to be done?" />
			<input type="submit" value="Add" />
		</form>
...
```

#### <span class="u-text--highlight">**Marking a todo as complete**</span>

Now, we are going to add some controls to the todo list. I have highlighted the added code. As you can see, we have added a <span class="inline-code">`Check FontAwesomeIcon`</span>. Upon clicking the check control, an action is dispatch which updates our current state and sets the <span class="inline-code">`isComplete`</span> flag to <span class="inline-code">`true`</span> for that particular todo.

You can always refer to this <a href="https://codesandbox.io/s/71j9q45y11" target="_blank">sandbox</a> for a live example to visualize it better.

```jsx
...
function reducer(state, action) {
	switch (action.type) {
		...
		case "COMPLETE_TODO":
			return {
				...state,
				todos: {
					...state.todos,
					[action.payload.id]: {
						...state.todos[action.payload.id],
						isComplete: true
					}
				}
			};
		...
	}
}
function controlHandler(id, operation) {
	switch (operation) {
		case "complete":
			dispatch({
				type: "COMPLETE_TODO",
				payload: {
					id
				}
			});
			break;
		default:
			console.log("This is odd.");
	}
}
...
	function renderContent() {
		...
		return (
			<ul id="todos">
				{keys.map(key => {
					...
					return (
						<li key={key}>
							<p className={isComplete ? "complete" : ""}>
								{todo}
							</p>
							<div class="controls">
								{!isComplete ? (
									<FontAwesomeIcon
										icon="check"
										title="Mark as Complete"
										className="control-icon"
										onClick={() =>
											controlHandler(key, "complete")
										}
									/>
								) : null}
							</div>
						</li>
					);
				})}
			</ul>
		);
	}
...
```

#### <span class="u-text--highlight">**Removing it once it has served its purpose**</span>

Just like complete functionality, we will add a delete icon with a click handler which filters the todos and update our state. Below is the complete working code of our app. I have highlighted the part added for removing a todo item.

You can always refer to this <a href="https://codesandbox.io/s/71j9q45y11" target="_blank">sandbox</a> for a live example to visualize it better.

```jsx
/*
	Author: Yomesh Gupta (https://www.twitter.com/yomeshgupta)
*/

import React, { useEffect, useState, useReducer } from 'react';
import ReactDOM from 'react-dom';
import { library } from '@fortawesome/fontawesome-svg-core';
import { faCheck, faTrash } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

import './styles.css';

library.add(faCheck, faTrash);

const initialState = {
	fetched: false,
	todos: {}
};

const TODOS = {
	1552406885681: {
		todo: 'Complete this blog post',
		isComplete: false
	},
	1552406885682: {
		todo: 'Add everything to this blog post',
		isComplete: false
	}
};

function reducer(state, action) {
	switch (action.type) {
		case 'REPLACE_TODOS':
			return { ...state, fetched: true, todos: action.payload };
		case 'UPDATE_TODOS': {
			return { ...state, todos: action.payload };
		}
		case 'ADD_TODO':
			return {
				...state,
				todos: {
					...state.todos,
					...action.payload
				}
			};
		case 'COMPLETE_TODO':
			return {
				...state,
				todos: {
					...state.todos,
					[action.payload.id]: {
						...state.todos[action.payload.id],
						isComplete: true
					}
				}
			};
		default:
			return state;
	}
}

function Loader() {
	return <div id="loader">Loading...</div>;
}

function Empty() {
	return <div id="empty">Seems kind of empty here...</div>;
}

function Todos() {
	const [task, setTask] = useState('');
	const [state, dispatch] = useReducer(reducer, initialState);
	const { fetched, todos } = state;
	const keys = Object.keys(todos);

	// Setting up an effect
	useEffect(function() {
		function fetchData() {
			new Promise((resolve, reject) => {
				// mocking API call
				setTimeout(() => resolve(TODOS), 2000);
			}).then(response => {
				// Updating state variable
				dispatch({
					type: 'REPLACE_TODOS',
					payload: response
				});
			});
		}
		fetchData();
	}, []);

	function saveHandler(e) {
		e.preventDefault();
		dispatch({
			type: 'ADD_TODO',
			payload: {
				[+new Date()]: {
					todo: task,
					isComplete: false
				}
			}
		});
		setTask('');
	}

	function controlHandler(id, operation) {
		switch (operation) {
			case 'complete':
				dispatch({
					type: 'COMPLETE_TODO',
					payload: {
						id
					}
				});
				break;
			case 'delete': {
				const clonedTodos = { ...todos };
				delete clonedTodos[id];
				dispatch({
					type: 'UPDATE_TODOS',
					payload: clonedTodos
				});
				break;
			}
			default:
				console.log('This is odd.');
		}
	}

	function renderContent() {
		if (!fetched) {
			return <Loader />;
		} else if (!keys.length) {
			return <Empty />;
		}
		return (
			<ul id="todos">
				{keys.map(key => {
					const value = todos[key];
					const { isComplete, todo } = value;
					return (
						<li key={key}>
							<p className={isComplete ? 'complete' : ''}>{todo}</p>
							<div class="controls">
								{!isComplete ? (
									<FontAwesomeIcon
										icon="check"
										title="Mark as Complete"
										className="control-icon"
										onClick={() => controlHandler(key, 'complete')}
									/>
								) : null}
								<FontAwesomeIcon
									icon="trash"
									title="Delete Todo"
									className="control-icon"
									onClick={() => controlHandler(key, 'delete')}
								/>
							</div>
						</li>
					);
				})}
			</ul>
		);
	}

	return (
		<div className="wrapper">
			<form method="#" onSubmit={saveHandler}>
				<input
					type="text"
					onChange={e => setTask(e.target.value)}
					value={task}
					placeholder="What needs to be done?"
				/>
				<input type="submit" value="Add" title="Add Todo" />
			</form>
			{renderContent()}
		</div>
	);
}
const rootElement = document.getElementById('root');
ReactDOM.render(<Todos />, rootElement);
```

Finally, our app is complete! Phew! However, we can go ahead and implement more functionalities like error handling, more controls, due date etc!
There are more hooks provided by React itself and we can even write our own custom hooks! Let's keep that for a part two of this blog post.

Quick Links: <a href="https://codesandbox.io/s/71j9q45y11" target="_blank">Working Sandbox</a> | <a href="https://github.com/yomeshgupta/react-hooks-todo-app" target="_blank">Github Repo</a>

A powerful and more advanced version of this Todo App, along with many more features, is shipped with a super awesome chrome extension called <a href="https://chrome.google.com/webstore/detail/backdrops/beanogjmmfajlfkfmlchaoamcoelddjf" target="_blank">Backdrops</a>. You should check it out.

> Backdrops is an amazing chrome extension which turns your boring chrome new tab to something more delightful and useful. Check it out <a href="https://chrome.google.com/webstore/detail/backdrops/beanogjmmfajlfkfmlchaoamcoelddjf" target="_blank">here</a>.

Hopefully, this article helped you in some way. It was originally posted <a href="https://devtools.tech/intro-to-react-hooks/" target="_blank" rel="noopener noreferrer">here</a>. Feel free to share your feedback <a href="https://www.twitter.com/yomeshgupta" target="_blank">here</a>.