DEV Community

Cover image for Managing State with useReducer  Hook.
Emmanuel Effiong
Emmanuel Effiong

Posted on

Managing State with useReducer Hook.

This is another built in React hook that helps with state management in React, but it has more capabilities and is used to manage complex state.
The reason why this is preferred is that, useReducer can be used to manage states that are closely related and share same values.
For example, lets say we want to manage a form that has an email field and a password field, and then you also want to check the validity of the email input and password input.

Imagine you had wanted to use the useState Hook for this., the code would have been robust with so many helper functions, but we'll have a cleaner code with the useReducer.

Before we dive into the code, lets understand useReducer and how it works..
useReducer is a react Hook that export 2 values that can be destructured, the current state and a dispatch function.
useReducer also takes in 3 properties, the reducer function, the initial state and and initial function.

useReducer

  • The current state will always be the current state after it has been changed, just like you have in useState.

  • The dispatch function is the state updating function, almost like useState, but here, the dispatch function returns an action which is an object with a type and a payload. The action type helps the reducer to know the function that is updating the state and the payload is the value that needs to be updated.

Another analogy is, dispatch function acts like the delivery man, delivery man holds the pizza name or type which is the action type, while the action payload is the pizza, pizza is the content and you want to update your stomach with 😂😂😂😂😂

  • The reducer function receives the latest state and the action that the dispatch function sent and then returns a new updated state

  • The initial state is the very first state you seed your useReducer hook with.

  • The initial function is rarely used, but it's a function you use to set your initial state.

Okay then, lets dive in and work on the code with what we've understood so far
useReducer
If you've noticed, i created our state object and seeded it into useReducer, i have also created my reducer function and also removed the initial function from the useReducer, since we won't be using it.

import React, {useReducer} from "react";

const reducerFxn = (state, action) => {

}

const initialState = {
  enteredEmail : "",
  emailIsValid : null,
  enteredPassword: "",
  passwordIsValid : null
}

const Login = () => {

  const [currentState, dispatchFxn] = useReducer(reducerFxn, initialState);

  const emailChangeHandler = (e) => {
    dispatchFxn({
      type:'ADD_EMAIL',
      payload: e.target.value
    })
  }

  const passwordChangeHandler = (e) => {
    dispatchFxn({
      type:'ADD_PASS',
      payload: e.target.value
    })
  }

  return <form>
        <div>
          <label htmlFor="email">E-Mail</label>
          <input type="email" id="email"
            value={state.enteredEmail}
            onChange={emailChangeHandler} />
        </div>

        <div>
          <label htmlFor="password">Password</label>
          <input type="password" id="password"
            value={state.enteredPassword}
            onChange={passwordChangeHandler} />
        </div>
      </form>
}

export default Login
Enter fullscreen mode Exit fullscreen mode

We have updated our jsx with a form, our code now has the emailChangeHandler and passwordChangeHandler, inside these handlers, you'll see our dispatch function doing what we said earlier, our dispatch function is returning an action object with type and payload. The types and payload are different for each input handler as you know.
The magic happens in the reducerFxn which you'll see below

import React, { useReducer } from "react";

const reducerFxn = (state, action) => {
  if (action.type === "ADD_EMAIL") {
    return {
      enteredEmail: action.payload,
      emailIsValid: action.payload.includes("@"),
      enteredPassword: state.enteredPassword,
      passwordIsValid: state.passwordIsValid,
    };
  }
  if (action.type === "ADD_PASS") {
    return {
      enteredEmail: state.enteredEmail,
      emailIsValid: state.emailIsValid,
      enteredPassword: action.payload,
      passwordIsValid: action.payload.trim().length >= 6,
    };
  }

  return state;
};

const initialState = {
  enteredEmail: "",
  emailIsValid: null,
  enteredPassword: "",
  passwordIsValid: null,
};
const Login = () => {
  const [currentState, dispatchFxn] = useReducer(reducerFxn, initialState);

  const emailChangeHandler = (e) => {
    dispatchFxn({
      type: "ADD_EMAIL",
      payload: e.target.value,
    });
  };

  const passwordChangeHandler = (e) => {
    dispatchFxn({
      type: "ADD_PASS",
      payload: e.target.value,
    });
  };

  const submitHandler = (e) => {
    e.preventDefault();
    console.log(currentState);
  };

  return (
    <form onSubmit={submitHandler}>
      <div>
        <label htmlFor="email">E-Mail</label>
        <input
          type="email"
          id="email"
          value={currentState.enteredEmail}
          onChange={emailChangeHandler}
        />
      </div>

      <div>
        <label htmlFor="password">Password</label>
        <input
          type="password"
          id="password"
          value={currentState.enteredPassword}
          onChange={passwordChangeHandler}
        />
      </div>
      <button>Submit</button>
    </form>
  );
};

export default Login;
Enter fullscreen mode Exit fullscreen mode

We've been able to update our state using our reducerfxn, let's walk through what i did there.,

  • Remember i told you that, reducerfxn takes in 2 values, the current state and the action(which contains what the dispatch function dispatched).

  • It checks for the type of dispatch and changes the state according to who sent it, in the case of the email, it checked it with if(action.type === 'ADD_EMAIL') block which returns true and it corresponds with what we dispatched and it will change the state with the payload as you have seen.

  • The enteredEmail field is updated with the action.payload which is equal to the event.target.value that we dispatched, now this is where useReducer is powerful, we now updated the emaiIsValid field instantly by checking if the payload contains '@' and this will return true or false. This saves us the extra stress of creating another useState hook if we wanted to update the state with useState.

To access the current states and maybe display them in your list item, you access the latest state with the currentState field that we destructured from useReducer.
To get the emailField will be currentState.emailField, and same with others..

So basically, useState is great for independent pieces of data, but useReducer is used when one state is dependent on each other like the case of enteredEmail and emailIsValid, and often times, you'll know when to use it, meanwhile you might not really need useReducer when all you have to do is change a single value of a particular state, because most at times you will be fine with useState, and using useReducer might just be an overkill.

Discussion (0)