We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies.

We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies. Less

We use cookies and other tracking technologies... More

Login or register
to publish this job!

Login or register
to save this job!

Login or register
to save interesting jobs!

Login or register
to get access to all your job applications!

Login or register to start contributing with an article!

Login or register
to see more jobs from this company!

Login or register
to boost this post!

Show some love to the author of this blog by giving their post some rocket fuel 🚀.

Login or register to search for your ideal job!

Login or register to start working on this issue!

Login or register
to save articles!

Login to see the application

Engineers who find a new job through WorksHub average a 15% increase in salary 🚀

You will be redirected back to this page right after signin

Blog hero image

A Complete Beginner's Guide to useReducer Hook [Part 2]

Abhinav Anshul 11 May, 2021 | 6 min read


Introduction


useReducer is a React Hook introduced late in October 2018, which allows us to handle complex state logic and action. It was inspired by Redux state management pattern and hence behaves in a somewhat similar way.

Oh! but don't we already have a useState hook to handle state management in React?

Well, yes! useState does the job pretty well. However, the useState hook is limited in cases where a component needs a complex state structure and proper sync with the tree. useReducer when combined with useContext hook could behave very similarly to Redux pattern and sometimes might be a better approach for global state management instead of other unofficial libraries such as Redux. As a matter of fact, the useReducer's API itself was used to create a simpler useState hook for state management.

According to React's official docs :

"An alternative to useState. Accepts a reducer of type (state, action) => newState, and returns the current state paired with a dispatch method."

A call to Reduce Method in JavaScript


To begin with useReducer, first, we need to understand how JavaScript's built-in Array method called Reduce works, which shares remarkable similarity with the useReducer hook.

The reduce method calls a function (a reducer function), operates on each element of an array and always returns a single value.

function reducer(accumulator, currentvalue, currentIndex, sourceArray){
  // returns a single value
}

arrayname.reduce(reducer)

As stated, the above reducer function takes in 4 parameters -

1. Accumulator : It stores the callback return values.

2. Current Value : The current value in the array being processed.

3. Current Index (optional) : The index of the current value in the array being processed.

4. Source Array : The source of the array on which reduce method was called upon.

Let's see reduce function in action, by creating a simple array of elements :

const items = [1, 10, 13, 24, 5]

Now, We will create a simple function called sum, to add up all the elements in the items array. The sum function is our reducer function, as explained above in the syntax

const items = [1, 10, 13, 24, 5]

function sum(a,b, c, d){
    return a + b
}

As we can see, I am passing four parameters named a, b, c, d, these parameters can be thought of as Accumulator, Current Value, Current Index, Source Array respectively.

Finally, calling the reduce method on our sum reducer function as follows

const items = [1, 10, 13, 24, 5];

function sum(a, b, c, d){
    return a + b;
}

const out = items.reduce(sum);

console.log(out);

OUTPUT :
59

Let's understand what's going on here :

When the reduce method gets called on the sum function, the accumulator ( here a) is placed on the zeroth index (1), the Current Value (here b) is on 10. On the next loop of the sum function, the accumulator adds up a + b from the previous iteration and stores it up in accumulator (a) while the current value(b) points to 13 now. Similarly, the accumulator keeps on adding the items from the array whatever the Current Index is pointing until it reaches the very end of it. Hence resulting in the summation of all the items in the array.

// We can do a console.log to check iteration in every function loop :

const items = [1,10,13,24,5];

function sum(a, b, c, d){
   console.log("Accumulator", a)
   console.log("Current Index", b)
 	 return a + b
}

const out = items.reduce(sum);

console.log(out);

'Accumulator' 1
'Current Index' 10
'Accumulator' 11
'Current Index' 13
'Accumulator' 24
'Current Index' 24
'Accumulator' 48
'Current Index' 5
53


In addition to this, there is an optional initial value, when provided will set the accumulator to the initial value first, before going out for the first index item in the array. The syntax could look like this :

items.reduce(sum, initial value)

While we just finished understanding how the reduce method works in JavaScript, turns out both the Redux library and the useReducer hook shares a common pattern, hence the similar name.

reduce v_s useReducer.png

How does useReducer works?


useReducer expects two parameters namely, a reducer function and an initial state.

useReducer(reducer, initialState)

Again the reducer function expects two parameters, a current state and an action and returns a new state.

function reducer(currentState, action){
	returns newState;
}


useReducer Hook in Simple State and Action


Based on what we have learnt so far, let's create a very simple counter component with increment, decrement feature.

We will begin by generating a JSX component :

import React from 'react';
import ReactDOM from 'react';

function App(){
  return (
    <div>
        <button onClick={handleIncrement}>+</button>
        <span>{state}</span>
        <button onClick={handleDecrement}>-</button>
    </div>
  );
}
// define a root div in a HTML file and mount as such
ReactDOM.render(<App />, document.getElementById("root"));

Create a reducer function, expecting a state and an action. Also, attach onClick events on both buttons and define the click functions within the App component :

import React, {useReducer} from 'react';
import ReactDOM from 'react';

function reducer(state, action){
  // return newState
}


function App(){
  function handleDecrement() {
    // ...
  }

  function handleIncrement() {
    // ...
  }

return (
  <div>
    <button onClick={handleIncrement}>+</button>
    <span>{state}</span>
    <button onClick={handleDecrement}>-</button>
  </div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));

Moving onwards, before we trigger useReducer hook, it is important to note that it returns an array of two values,

const state = useReducer[0]
const dispatch = useReducer[1]

We can simplify the above, using array destructuring (a best practice) :

const [state, dispatch] = useReducer(reducer, intialState)

Now, coming back to our counter component and including the above useReducer snippet in it

function reducer(state, action){
  if (action === "increment") {
    return state + 1;
  } 
  else if (action === "decrement") {
    return state - 1;
  } 
  else null;
}


function App(){
  function handleDecrement() {
    dispatch("decrement");
  }

  function handleIncrement() {
    dispatch("increment");
  }

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

return (
  <div>
    <button onClick={handleIncrement}>+</button>
    <span>{state}</span>
    <button onClick={handleDecrement}>-</button>
  </div>
);
}


Link to codepen

The handleIncrement and handleDecrement function returns a dispatch method with a string called increment and decrement respectively. Based on that dispatch method, there is an if-else statement in the reducer function which returns a new state and eventually triggering (overriding in a way) the state in useReducer.

According to the official docs, always use Switch statements in the reducer function (you already know this if you have worked with Redux before), for more cleaner and maintainable code. Adding more to this, it is advisable to create an initial state object and pass a reference to the useReducer

const initialState = { 
  count: 0 
  // initialize other data here
}
const [state, dispatch] = React.useReducer(reducer, intialState);


Join our newsletter
Join over 111,000 others and get access to exclusive content, job opportunities and more!

useReducer Hook in Complex State and Action


Let's see the same counter component, building it with what we have learnt so far but this time with a little complexity, more abstraction, also best practices.

const initialState = {
  count: 0
};

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.value };
    case "decrement":
      return { count: state.count - action.value };
  }
}

function App() {
  const [state, dispatch] = React.useReducer(reducer, initialState);

  return (
    <div>
      <button onClick={() => dispatch({ type: "increment", value: 5 })}>
        +
      </button>
      <span>{state.count}</span>
      <button onClick={() => dispatch({ type: "decrement", value: 5 })}>
        -
      </button>
    </div>
  );
}


Link to codepen

What has changed?

  1. Instead of passing a value directly to the useReducer hook, we have an object initialised with a count property set to zero. This helps in cases when there are more than a single property to be initialised also easier to operate on an object.
  2. As we discussed earlier, if-else has been modified to switch based statements in the reducer function.
  3. The dispatch method is now object-based and provides two properties type and value. Since the dispatch method triggers action, we can switch statements in the reducer function using action.type. Also, the new state can be modified by using a dynamic value which can be accessed on action.value


Dealing with Multiple useReducer Hook


When dealing with multiple state variables that have the same state transition, sometimes it could be useful to use multiple useReducer hook that uses the same reducer function.

Let's see an example :

const initialState = {
  count : 0
}

function reducer(state, action) {
switch (action) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
      default : 
      return state
  }
}

function App() {
  const [state, dispatch] = React.useReducer(reducer, initialState);
  const [stateTwo, dispatchTwo] = React.useReducer(reducer, initialState);

return (
  <>
    <div>
        <button onClick={() => dispatch('increment')}>+</button> 
        <span>{state.count}</span>
        <button onClick={() => dispatch('decrement')}>-</button>
    </div>
    <div>
        <button onClick={() => dispatchTwo('increment')}>+</button>
        <span>{stateTwo.count}</span>
        <button onClick={() => dispatchTwo('decrement')}>-</button>
    </div>
  </>
);
}



ReactDOM.render(<App />, document.getElementById("root"));



Here we are using two useReducer hook with different dispatch and state triggering the same reducer function.

Link to codepen


useState v/s useReducer


Here is a quick rundown, comparing both the hooks :

useState v_s useReducer.png


useReducer's Rendering Behavior


React renders and re-renders any useReducer component very similar to the useState hook.

consider the following contrived example where + increments the count by 1, - decrements the count by 1 and Reset restores the count value to 0.

function App(){
  const [count, dispatch] = useReducer(reducer, initialState)
  console.log("COMPONENT RENDERING");
    return (
      <div>
          <div>{count}</div>
          <button onClick={() => {dispatch('increment')}>+</button>
          <button onClick={() => {dispatch('decrement')}>-</button>
          <button onClick={() => dispatch('reset')}>Reset</button>	
      </div>
	)
}

The above App component :

1. Re-render every time the count state changes its value, hence logging out the COMPONENT RENDERING line.

2. Once, the reset button has been clicked, the subsequent clicks to the reset button won't re-render the App component as the state value is always zero.



While we just finished reading how rendering happens in the context of useReducer, we have reached the end of this article!

Some Important Resources that I have collected over time:

1. https://reactjs.org/docs/hooks-reference.html#usereducer

2. https://geekflare.com/react-rendering/

3. https://kentcdodds.com/blog/should-i-usestate-or-usereducer

4. https://kentcdodds.com/blog/application-state-management-with-react

Loved this post? Have a suggestion or just want to say hi? Reach out to me on Twitter

Related Issues

cosmos / gaia
  • Started
  • 0
  • 6
  • Intermediate
  • Go
cosmos / gaia
  • Started
  • 0
  • 3
  • Intermediate
  • Go
cosmos / ibc
  • Open
  • 0
  • 0
  • Intermediate
  • TeX

Get hired!

Sign up now and apply for roles at companies that interest you.

Engineers who find a new job through WorksHub average a 15% increase in salary.

Start with GitHubStart with Stack OverflowStart with Email