DEV Community

Cover image for For intricate state handling, try out the useReducer() Hook
Anshuman Mahato
Anshuman Mahato

Posted on

For intricate state handling, try out the useReducer() Hook

So, lately, I have been working on a form component. It was a registration form that had quite a few fields in it. The state management for this was not complex, but it was repetitive. Creating a state for each input field and updating it whenever the user interacts with them, I was writing the same code with minor differences.

let [fname,setFname] = useState("");
let [lname,setLname] = useState("");
let [email,setEmail] = useState("");
...
Enter fullscreen mode Exit fullscreen mode

Also, I felt that all these pieces must be related to one another rather than being separate entities. So, I switched to using a single state with an object storing all input fields.

let [formData, setFormData] = useState({
   fname: "",
   lname: "",
   email: "",
   ... 
});
Enter fullscreen mode Exit fullscreen mode

Even though the state was centralized, the event handlers still managed the state update logic.

const changeEmail = (e) => {
     const regex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;

     if(e.target.value.match(regex))
         setFormData({...state, email: e.target.value});
};
Enter fullscreen mode Exit fullscreen mode

I was looking for some way to centralize this as well. That's when I came across the useReducer() hook, a utility for complex state management.

Want to know about this? Just follow along with me.

What is this useReducer() hook, and how does it work?

The useReducer() hook is an alternative to the useState() hook that helps to manage complex states in a React application. In fact, the useState() hook uses the useReducer() hook behind the scenes itself.

The useReducer() hook takes a reducer function and the initial state as its parameter and returns an array. The first element of this array is the state object, and the second element is the dispatch function. At first, it might look very similar to useState(). But the working is very different here.

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

The state and the initialState is obvious. But, what is dispatch and reducer? Let's try to understand how all this works.

Reducer, Actions and Dispatch

The dispatch is analogous to the update function returned by the useState() hook. Both are used to update the state. The difference lies in how they work. Unlike the update function, where we pass the next state directly, we pass an Action here.

Now, you may ask, "What is this Action?". Well, An Action is an object that specifies how we want to update our state. You may structure your Action object as you wish. But conventionally, we have two fields in this object - type and payload. The type specifies the kind of update that we want to perform. The payload takes any external values that might be needed for the update.

dispatch({type:"ACTION TYPE", payload:"values to pass"});
Enter fullscreen mode Exit fullscreen mode

What does the dispatch do with this Action object? It passes it to its associated reducer. The reducer is a function that maintains the logic for state updates for every action type. It takes the current state and an Action object as arguments and returns the updated state per the requested action. After this, the component is rendered according to the update state.

State update flow in useReducer() hook

Let's try this out

Let us build a simple Counter App to see this working (cliche, I know). The application will have four functions -

  1. We can increment the count by 1.
  2. We can decrease the count by 1.
  3. We can add some value to the count.
  4. We can subtract some value from the count.

Let's begin with our initial state. It will only contain a field for the count.

const initState = {count: 0};
Enter fullscreen mode Exit fullscreen mode

Now, we implement our reducer function.

function reducer(state,action) {
   const {type,payload} = action;
   const {count} = state;
   switch(type) {
      case "INCREMENT": return {...state, count: count + 1};
      case "DECREMENT": return {...state, count: count - 1};
      case "ADD": return {...state, count: count + payload.value};
      case "SUBTRACT": return {...state, count: count - payload.value};
      default: return state;
   }
}
Enter fullscreen mode Exit fullscreen mode

It's time to implement our Counter component.

function Counter() {
  const [state, dispatch] = useReducer(reducer, initState);
  const [value, setValue] = useState(0);
  return (
    <div>
      <h1>{state.count}</h1>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>Decrement</button>
      <div>
        <input
          type="number"
          value={value}
          onChange={(e) => setValue(parseInt(e.target.value))}
        />
        <button onClick={() => dispatch({ type: "ADD", payload: { value } })}>
          Add
        </button>
        <button
          onClick={() => dispatch({ type: "SUBTRACT", payload: { value } })}
        >
          Subtract
        </button>
      </div>
    </div>
  );
}

export default Counter;
Enter fullscreen mode Exit fullscreen mode

And with this, our component is complete. Copy this code and play around with it to understand things better.

Things to consider

Here are some points to remember when working with the useReducer() hook.

  1. Do not use them anywhere and everywhere. Use it only where the structure of the state is somewhat complex. Otherwise, you'll be writing unnecessarily lengthy codes. For simple states, stick to the useState() hook.

  2. Storing action types as constants is better than using literals. It helps in avoiding bugs due to typos.

  3. Always return a new object from the reducer when updating the state. React makes a shallow comparison when comparing new and old states. Updating a particular field will not trigger re-renders.

That's all Folks

That's a wrap for now, folks! I hope this article was insightful to you. I look forward to your insights and feedback. Let's keep this conversation going in the comments below!

And hey, if you want to connect beyond these pages, these are the places where you can find me!

Twitter    LinkedIn    GitHub

Until next time, stay curious and keep exploring!🌟 Thank You for reading this far. 😊

Top comments (0)