useReducer Hook to the rescue!

ciphertron

Pritish Samal

Posted on February 6, 2022

useReducer Hook to the rescue!

Hello folks πŸ‘‹
Have you ever felt the need of an easier way to manage complex state in your React application without having to install heavy dependencies? You've arrived at the right place. Let us talk about the useReducer() hook provided by React. You must be familiar with the useState() hook in React. It's basically a hook that lets you add state to a functional value. The useReducer hook, an alternative to the useState hook, also lets you manage the state when you've a complex state logic.

There are various state management libraries like Redux, Recoil, Easy Peasy, etc. Personally, I have used Redux to manage state and can say that the usage of useReducer hook is pretty much similar to that of Redux. However, there are some differences between Redux and the useReducer hook. The useReducer hook comes in-built with React but Redux is a 3rd party dependency which was built as a state management tool for JavaScript applications and not specifically for React. Also, it takes a lot of boilerplate code to set-up Redux but setting up useReducer is pretty minimal and straight forward. Today, we'll learn about managing state with useReducer hook by building a Notes Application.

So, I'll be demonstrating two basic functionalities of creating and deleting a note using useReducer(). You can find the code of the application here. First, let us create a basic controlled form which will store the title and body of the note.

<form>
  <input
    type="text"
    placeholder="Note Title"
    value={title}
    onChange={(e) => setTitle(e.target.value)}
  />
  <input
    type="text"
    placeholder="Note Body"
    value={body}
    onChange={(e) => setBody(e.target.value)}
  />
  <input type="submit" value="Add Note" />
</form>
Enter fullscreen mode Exit fullscreen mode

So, we'll be using two states (one to store the note title and the other to store note body).

const [title, setTitle] = useState("");
const [body, setBody] = useState("");
Enter fullscreen mode Exit fullscreen mode

In order to use the useReducer hook, we need to have our actions and reducers defined. The concept of actions and reducers in this case has the same use-case as that of redux. Reducer is nothing but a pure function that takes an action and the previous state of the application and returns a new state. Actions on the other hand describes what happened and it is the reducer's job to return the new state based on that action.

In this case we need two actions i.e. create and delete note which can be written as:

const ACTIONS = {
  ADD_NOTE: "add-note",
  DELETE_NOTE: "delete-note"
};
Enter fullscreen mode Exit fullscreen mode

We have our Let's initialise a useReducer() hook below πŸ‘‡

const [notes, dispatch] = useReducer(reducer, []);
Enter fullscreen mode Exit fullscreen mode

Here, notes is the state which will store all the notes and the dispatch is the function of the global store using which we can dispatch an action. The empty array ([]) is the initial state of the notes array.

Now, let us define the onSubmit handler of our form:

<form onSubmit={handleSubmit}>

const handleSubmit = (e) => {
 e.preventDefault();
 dispatch({ type: ACTIONS.ADD_NOTE, payload: { title: title, body: body } });
 setTitle("");
 setBody("");
}
Enter fullscreen mode Exit fullscreen mode

The dispatch function will take in an object with a field named type which will contain the type of action we want to perform when our form is submitted. Here, we'll be passing ACTIONS.ADD_NOTE action. Also, we can pass an optional field named payload(name can be anything, not necessarily payload) and can be any type of value.

It represents the payload of the action. Any information about the action that is not the type or status of the action should be part of the payload field.

When it comes to reducer, let's make one for ADD_NOTE action:

const reducer = (notes, action) => {
  switch (action.type) {
    case ACTIONS.ADD_NOTE:
      return [...notes, newNote(action.payload.title, action.payload.body)];
    default:
      return notes;
  }
};
Enter fullscreen mode Exit fullscreen mode

The newNote() function that you can see above will only return a new note object with all the properties in it. It will look likeπŸ‘‡

const newNote = (title, body) => {
  return { id: Date.now(), title: title, body: body };
};
Enter fullscreen mode Exit fullscreen mode

On the dispatch of the ADD_NOTE action, it will execute its corresponding reducer and then the notes state will be updated with the new note.

In-order to display all the Notes, we can map through the notes array and we can display each note like belowπŸ‘‡

{notes.map((note) => (
        <Note key={note.id} note={note} />
))}
Enter fullscreen mode Exit fullscreen mode

Where, the Note component is the structure of a note and is imported in App.js.

export default function Note({ note }) {
  return (
    <Wrapper>
      <NoteTitle>{note.title}</NoteTitle>
      <NoteBody>{note.body}</NoteBody>
      <DeleteIcon>
        <Trash2 />
      </DeleteIcon>
    </Wrapper>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now, you can see when you submit the form a new note gets added in the screen.

PS: I've used Styled Components to style the jsx elements(personal preference) and feather icons using react-feather.

The first part of our application is done i.e. adding a note. Now moving on to deleting a note, we have its corresponding action as DELETE_NOTE. So, in order to delete a note, we require its id and this action needs to be dispatched when the delete button gets clicked which by the way is present in the Note.jsx component. Thus in-order to do the same, we can pass the dispatch function as a prop and then dispatch the action inside the note component.

Passing dispatch function as propπŸ‘‡

<Note key={note.id} note={note} dispatch={dispatch} />
Enter fullscreen mode Exit fullscreen mode

And then, destructure it in Note.jsx and use it when the delete button is clicked. We can do that like such belowπŸ‘‡

export default function Note({ note, dispatch }) {
  return (
    <Wrapper>
      <NoteTitle>{note.title}</NoteTitle>
      <NoteBody>{note.body}</NoteBody>
      <DeleteIcon
        onClick={() =>
          dispatch({ type: ACTIONS.DELETE_NOTE, payload: { id: note.id } })
        }
      >
        <Trash2 />
      </DeleteIcon>
    </Wrapper>
  );
}
Enter fullscreen mode Exit fullscreen mode

Since we require the note id inside the reducer, thus we passed it inside the payload object. Now, the final task remaining is to write a reducer function for DELETE_NOTE actionπŸ‘‡

case ACTIONS.DELETE_NOTE:
      return notes.filter((note) => note.id !== action.payload.id);
Enter fullscreen mode Exit fullscreen mode

The above snippet basically traverses through the notes array and it makes another array with all the notes whose id doesn't matches with the note.payload.id.

That was basically it. Your Note Application is up and running with create and delete functionalities. I know my CSS sucks but I'm trying to become better at it. Please bear with meπŸ˜‚

You can find the full codebase here. Before concluding, let me quickly walk you through the directory structure:

β”œβ”€β”€ public
β”‚   β”œβ”€β”€ index.html
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ components
β”‚   β”‚   β”œβ”€β”€ Note.jsx // Note component
β”‚   β”‚   β”œβ”€β”€ Navbar.jsx // Navbar component
β”‚   β”œβ”€β”€ store
β”‚   β”‚   β”œβ”€β”€ actions.js // Contains all the actions
β”‚   β”‚   β”œβ”€β”€ reducers.js // contains all the reducer functions
β”‚   β”œβ”€β”€ App.js
β”‚   β”œβ”€β”€ index.js
β”‚   β”œβ”€β”€ styles.css
β”œβ”€β”€ package.json
Enter fullscreen mode Exit fullscreen mode

This type of structure can come handy when you've a complex state logic. Here we keep the actions and reducers separately inside a store directory and you just import them and use wherever required.

That was all about the useReducer() hook. Hope you liked it. Do like, share and comment your suggestions below. I'm trying to be consistent in publishing articles now xD. There are a few other blogs lined up too. They'll be on the Context API in React, beginner's guide to Kubernetes, Golang Minutes, my MLH Prep Fellowship experience, all about LFX Mentorship Program, GitHub Campus Experts program and many more. If you're interested in such content, do follow me dev.to/ciphertron. You can find more about me at pritishsamal.tech.

Relevant Links:

πŸ’– πŸ’ͺ πŸ™… 🚩
ciphertron
Pritish Samal

Posted on February 6, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related