I’ll show you how to create and style our components by App.js and AppStyles.js . We export JavaScript objects for styling and import them in our component files, respectively.

For other components, the logic is as same as this example. Also, I omit the -Styles suffix files later in this tutorial, but you could find the source code in my repository.

Create src/styles/AppStyles.js and src/components/App.js (remove src/App.js ):

A CSS-in-JS solution

The App.js boilerplate

Import the styles in App.js and use const classes = useStyles() to get the value in the function .

For example, you used to type at line 9:

<header className="header">

However, we do not use CSS in our app, and we have to write className={classes.header} because classes is now a JS object.

Make sure to change the importing in your src/index.js to

import App from './components/App';

Design the blueprint (context and reducer)

Before moving on, let’s take a look at what we need. Traditionally, we have to keep states and define methods in our central parent component ( TodoApp in this case.) In this tutorial, we’ll use Context and Hooks to simplify the logic and centralize the states and methods.

We need a context file to centralize the props. Also, a reducer could reduce the complexity a lot.

Let’s create src/reducers/todos.reducer.js and src/contexts/todos.context.js :

A centralized place to track the state of todos

There’s nothing special; pass in a state and return it.

A conventional way to create a context

We have to createContext and export it, so we could import this context and use it later.

As for the useReducer , it takes two parameters, the todosReducer we just created and initializerArg (default value). It’ll return an array of the state ( todos ) and a method to update that state, which we are still not using in this commit.

Import TodoApp in App.js :

Add TodoApp in App.js.

Create TodoApp.js and TodoList.js :

All subcomponents of TodoList can consume the context of TodosProvider.

A TodoList that consumes todos in the context

In TodoList , useContext(TodosContext) provides the todos in the TodosContext for us.

Now, you should see some dummy todos listed in your browser.

Create the TodoForm

Import TodoForm in TodoApp.js :

Add TodoForm in TodoApp.js.

useInputState.js

We could customize a hook to write less code. Create src/hooks/useInputState.js :

A customized hook that handles form binding

We create a reusable hook that requires an initialValue , in which it generates a general state, value , and a general method, setValue . If we want to use the input hook later on (you’ll see it in this tutorial), we don’t bother to write duplicate code.

TodoForm should have the ability to add a todo, so we require our reducer to handle this action for us. Create src/constants/actions.js to centralize all actions’ constants.

Modify todos.reducer.js as the following. Here, we use uuid to generate a unique id for each todo.

Use ` uuid to generate a unique id for each todo.

Also, we have to modify the todos.context.js . DispatchContext provides dispatch action, which is returned from useReducer(todosReducer, defaultTodos) , for children components.

Add DispatchContext and the returned value (dispatch) of useReducer(todosReducer, defaultTodos).

Now, create TodoForm.js and TodoFormStyles.js :

With the helper hook, we make our ` TodoForm` both tiny and cute.

Create the Todo component

Add Font Awesome CSS link in public/index.html :

Add constants in actions and actions of removing a todo and toggling a todo in todos.reducer.js :

Import Todo in TodoList :

Finally, create Todo.js and TodoStyles.js :

We use memo to make Todo a pure component, preventing unnecessary re-rendering. The dispatch in Todo works similarly to TodoForm ; they both consume the context provided by DispatchContext .

Add the EditTodoForm

Now, we need another helper hook to toggle showing a EditTodoForm or showing a Todo .

Create src/hooks/useToggleState.js :

In our Todo.js , add the following lines:

We also need the action of editing a todo. Add constants in src/constants/actions.js and add the following lines in src/reducers/todos.reducer.js :

Finally, we can create EditTodoForm.js and EditTodoFormStyles.js :

You see! We again use the customized hook useInputState , which was created for TodoForm . Centralization of a single hook allows us to avoid writing duplicate code and reduce the chance to have bugs.

Persist our data in the browser

Open the console in the browser (⌘ + ⌥ + I in macOS), type window.localStorage , and press enter. You’ll find that there’s nothing in the localStoarge , and that’s the place in which we could store our data.

Create src/hooks/useLocalStorageReducer.js :

We hook our reducer by giving it the third parameter.

Pass in a parameter key to the hook to acknowledge our reducer that we want to store our data with a specific key.

Therefore, instead of directly using the defaultValue , the reducer will try to parse window.localStorage.getItem(key) first, and only if it fails will it use the defaultValue .