Understanding the useReducer Hook
You probably have some experience using Redux (react-redux
) with class components, and if that is the case, then you will understand how useReducer
works. The concepts are basically the same: actions, reducers, dispatch, store, and state. Even if, in general, it seems very similar to react-redux, they have some differences. The main difference is that react-redux provides middleware and wrappers such as thunk, sagas, and many more besides, while useReducer
just gives you a dispatch
method that you can use to dispatch plain objects as actions. Also, useReducer
does not have a store by default; instead, you can create one using useContext
, but this is just reinventing the wheel.
Let’s create a basic application to understand how useReducer
works. You can start by creating a new React app:
npx create-vite reducer --template react-ts
Then, as always, you can delete all files in your src
folder except App.tsx
and index.tsx
to start a brand-new application.
We will create a basic Notes
application where we can list
, delete
, create
, or update
our notes using useReducer
. The first thing you need to do is import the Notes
component, which we will create later, into your App
component:
import Notes from './Notes'
function App() {
return (
<Notes />
)
}
export default App
Now, in our Notes
component, you first need to import useReducer
and useState
:
import { useReducer, useState, ChangeEvent } from 'react'
Then we need to define some TypeScript types that we need to use for our Note
object, the Redux
action, and the action types:
type Note = {
id: number
note: string
}
type Action = {
type: string
payload?: any
}
type ActionTypes = {
ADD: 'ADD'
UPDATE: 'UPDATE'
DELETE: 'DELETE'
}
const actionType: ActionTypes = {
ADD: 'ADD',
DELETE: 'DELETE',
UPDATE: 'UPDATE'
}
After this, we need to create initialNotes
(also known as initialState
) with some dummy notes:
const initialNotes: Note[] = [
{
id: 1,
note: 'Note 1'
},
{
id: 2,
note: 'Note 2'
}
]
If you remember how the reducers work, then this will seem very similar to how we handle the reducer using a switch
statement, so as to perform basic operations such as ADD
, DELETE
, and UPDATE
:
const reducer = (state: Note[], action: Action) => {
switch (action.type) {
case actionType.ADD:
return [...state, action.payload]
case actionType.DELETE:
return state.filter(note => note.id !== action.payload)
case actionType.UPDATE:
const updatedNote = action.payload
return state.map((n: Note) => n.id === updatedNote.id ? updatedNote : n)
default:
return state
}
}
Finally, the component is very straightforward. Basically, you get the notes and the dispatch method from the useReducer
Hook (similar to useState
), and you need to pass the reducer
function and initialNotes
(initialState
):
const Notes = () => {
const [notes, dispatch] = useReducer(reducer, initialNotes)
const [note, setNote] = useState<string>('')
...
}
Then, we have a handleSubmit
function to create a new note when we write something in the input. Then, we press Enter:
const handleSubmit = (e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault()
const newNote = {
id: Date.now(),
note
}
dispatch({ type: actionType.ADD, payload: newNote })
}
Finally, we render our Notes
list with map
, and we also create two buttons, one for delete
and one for update
, and then the input should be wrapped into a <form>
tag:
return (
<div>
<h2>Notes</h2>
<ul>
{notes.map((n: Note) => (
<li key={n.id}>
{n.note} {' '}
<button onClick={() => dispatch({ type: actionType.DELETE, payload: n.id })}>
X
</button>
<button
onClick={() => dispatch({ type: actionType.UPDATE, payload: {...n, note} })}
>
Update
</button>
</li>
))}
</ul>
<form onSubmit={handleSubmit}>
<input
placeholder="New note"
value={note}
onChange={e => setNote(e.target.value)}
/>
</form>
</div>
)
export default Notes
If you run the application, you should see the following output:
Figure 8.22: React DevTools
As you can see in the React DevTools, the Reducer
object contains the two notes that we have defined as our initial state.
Now, if you write something in the input and you press Enter, you should be able to create a new note:
Figure 8.23: Creating a new note
Then, if you want to delete a note, you just need to click on the X button. Let’s remove Note 2:
Figure 8.24: Deleting a note
Finally, you can write anything you want in the input, and if you click on the Update button, you will change the note value:
Figure 8.25: Updating a note
Nice, huh? As you can see, the useReducer
Hook is pretty much the same as redux
in terms of the dispatch method, actions, and reducers, but the main difference is that this is limited just to the context of your component and its child, so if you need a global store to be accessible from your entire application, then you should use react-redux
instead.