React 16.7.0-alpha introduced Hooks, a new feature to reuse stateful logic between components. Hooks are self-contained blocks of functionality that can be easily extracted and shared, also as external dependencies. In addition, this approach doesn't require to change the component hierarchy.
I encourage you to read the official documentation as well as to watch the video introduction from React Conf 2018 before reading further.
In this article, we will build a simple Todo list using React Hooks. The code is available as this codesanbox.
Single Task
Let's start with the component representing a single task:
::: label ./src/components/Task.js :::
import React, { memo } from "react";
export default memo(({ text, complete, onClick }) => (
<li
class="task__item"
key={text}
style={{
textDecoration: complete ? "line-through" : "",
color: complete ? "#CCC" : ""
}}
>
<div class="mr-auto">{text}</div>
<div>
<a onClick={onClick}>
<i
class="fa fa-check"
style={{
color: complete ? "#75b51b" : ""
}}
/>
</a>
</div>
</li>
));
It displays a task text along with a check icon. Once the icon is clicked, the handler passed as a onClick
prop is executed. There are also some styling changes in relation to complete
boolean for each task: completed tasks are crossed-through and written using a lighter color.
List of Tasks
Next, there is a list of tasks with an input field at the top add new tasks to the list.
::: label ./src/components/TaskList.js :::
export default () => {
const [tasks, setTasks] = useState([]);
const toggleComplete = i => {
const findTask = (task, k) =>
k === i ? { ...task, complete: !task.complete } : task;
setTasks(tasks.map(findTask));
};
return (
<div className="App">
<TaskInput
onSubmit={text => setTasks([{ text, complete: false }, ...tasks])}
/>
<ul class="task__list">
{tasks.map((task, idx) => (
<Task {...task} onClick={() => toggleComplete(idx)} />
))}
</ul>
</div>
);
};
Initially, our task list is empty as defined via useState
hook with an empty list as its parameter. This part represents the state of TaskList
component.
The component iterates over the list of tasks and renders each of them as Task
. We also define a helper function toggleComplete
which uses the task position within the list as the parameter to refer to each of them.
Adding New Task
Lastly, there is TaskInput
component defining an onSubmit
handler that prepends a new task (with complete
set as false
) to the list each time there is a new entry.
::: label ./src/components/TaskInput.js :::
import React, { useState } from "react";
const useInputValue = initialValue => {
const [value, setValue] = useState(initialValue);
return {
value,
onChange: e => setValue(e.target.value),
resetValue: () => setValue("")
};
};
export default ({ onSubmit }) => {
const { resetValue, ...text } = useInputValue("");
return (
<form
onSubmit={e => {
e.preventDefault();
onSubmit(text.value);
resetValue();
}}
>
<div class="form-group">
<input
type="text"
class="form-control form-control-lg"
id="name"
placeholder="Read about React Hooks"
{...text}
/>
<small class="form-text text-muted">
Type something in the input field above and press Enter.
</small>
</div>
</form>
);
};
The TaskInput
component defines its own hook called useInputValue
to declare the state and additional handlers for input fields.
There is a single input field in this application, so useInputValue
is not necessary. We could've used useState
directly. The example shows how easily hooks can be combined to build bigger blocks of abstraction that can be reused across our application.
You can play with the application using this codesandbox.