useReducer Hook?

useReducer Hook?

In this article, we will learn about useReducer Hook

The useReducer Hook is similar to the useState Hook.

What is useReducer Hook?

useReducers is a hook in React that allows you to manage state in a more complex way than useState.

It is a function that accepts two arguments, a reducer function and an initial state, and returns an array with two elements: the current state and a dispatch function.

The basic syntax of using useReducer is as follows:

const [state, dispatch] = useReducer(reducer, initialState);

The reducer function takes in the current state and an action, and returns a new state based on the action.

The dispatch function is used to trigger the reducer function with an action, which then updates the state. This hook is useful for managing more complex state in your React components.

Here's an example to illustrate the usage of useReducer:

import React, { useReducer } from 'react';

// Initial state
const initialState = {
  count: 0,
};

// Reducer function
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    default:
      return state;
  }
};

const Counter = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  const handleIncrement = () => {
    dispatch({ type: 'increment' });
  };

  const handleDecrement = () => {
    dispatch({ type: 'decrement' });
  };

  const handleReset = () => {
    dispatch({ type: 'reset' });
  };

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleDecrement}>Decrement</button>
      <button onClick={handleReset}>Reset</button>
    </div>
  );
};

export default Counter;

In the example above, we define an initial state object that contains a count property. We also define a reducer function that specifies how the state should be updated based on different actions: increment, decrement, and reset. The reducer function returns a new state object based on the action type.

Inside the Counter component, we use the useReducer Hook to initialize the state and obtain a dispatch function. The dispatch function is used to dispatch actions, which triggers the state update based on the reducer logic.

We define event handlers like handleIncrement, handleDecrement, and handleReset that call dispatch with the appropriate action types. When these event handlers are triggered, the reducer function is called, and the state is updated accordingly.

The component renders the current count from the state, along with buttons to increment, decrement, and reset the count. When any of these buttons are clicked, the state is updated based on the dispatched actions, and the component re-renders with the updated count.

useReducer is particularly useful when managing complex state that involves multiple values or when the state transitions depend on the previous state. It provides a way to centralize and organize state management logic in a predictable manner, making it easier to reason about and test.

Important Things to Know

Now that we know how to use this Hook, it is important to know some details.

1. Local 'Store'

If you know Redux, you know that it has a centralized 'store' where the app can access variables from any of its components.

However, keep in mind that useReducer only stores data locally within its component.

2. Reducers are pure

Notice that the reducer function must be a pure function. This means that the function returns the same value if the same arguments are passed and there are no side effects.

Let's say we alter our counter app example reducer function to directly mutate the value of state instead of returning as a new value. The app will no longer work.

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return state++; //directly increasing state
    case "decrement":
      return state--; //directly decreasing state
    case "reset":
      return 0;
    default:
      throw new Error();
  }
}

3. Lazy Initialization

As mentioned earlier, the Hook optionally takes in a 3rd argument: init. init is a function that sets the initialState with init(initialArg).

Back to our counter app example, we can use the init function to set our initial state as follows:

function init(initialState) {
  return initialState;
}

Then we edit our reducer function to set state to its initial value when 'type' is "reset".

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return state + 1;
    case "decrement":
      return state - 1;
    case "reset":
      return init(action.payload); //used init instead of hard code as 0
    default:
      return;
  }
}

Finally, we make some changes to our App component and Hook.

function App() {
  //add init as the 3rd argument
  const [state, dispatch] = useReducer(reducer, initialState, init);

  return (
    <div>
      <h1> Count: {state}</h1>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <!--Add a payload property with initialState as the value-->
      <button onClick={() => 
        dispatch({ type: "reset", payload: initialState})}>Reset</button>
    </div>
  );
}