DEV Community

Cover image for useReducer() Hook:  Let me dispatch it
Rajshekhar Yadav
Rajshekhar Yadav

Posted on • Originally published at yadavrajshekhar.hashnode.dev

useReducer() Hook: Let me dispatch it

Introduction

In previous article, I have tried to explain the useState hook. The word reducer might present a thought about Redux but don’t worry you don’t have to think about it. I will try to explain what useReducer is and how to use it.

When it comes for managing complex state logic , useState might not be a good idea. There comes the idea about useReducer .

Let’s dive in !!!

https://media.giphy.com/media/iMBEgyXkFBtdCFS93i/giphy.gif

UseReducer()

useReducer() is used for storing and updating states. Basically with reducer you trigger some action to view, those event are listened by reducer who has the logic for storing or updating the state. When the state is updated your component re-render.

Anatomy of useReducer()

The useReducer(reducer, initialState) hook accepts 2 parameter. The reducer function as a first parameter and the initial state as a second parameter. The hook then returns an array of 2 items: the current state and the dispatch function.

useReducer returns an array of length two, whose first item as current stated and second item is dispatch functions.

Declaring the useReducer()

Import the useReducer() package from react

import React, {useReducer} from 'react';
Enter fullscreen mode Exit fullscreen mode

Initialising the useReducer ()

We can initialising useReducer by following way.

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

Now we will try to decipher what is the meaning state , dispatch , reducer , initialState these terms.

Let’s create a counter app. With the help of this app I will try to explain the meaning of aforementioned terms.

image.png

Initial State

This is the default value of our component’s state when it renders first time.

const initialState = {count: 0}; // At Line 13
Enter fullscreen mode Exit fullscreen mode

Dispatch Function

The dispatch function is the second items returned from the useReducer . It accepts and object that represents the type of action we want to perform. It sends an action to reducer function and reducer function perform the appropriate job ( update the state) on the basis of received action.

The actions that will be dispatched by our components should always be represented as one object with the type and payload key, where type stands as the identifier of the dispatched action and payload is the piece of information that this action will add to the state.

onPress={() => {
            dispatch({type: 'Decrement', payload: {}});
          }}
Enter fullscreen mode Exit fullscreen mode

Reducer Function

The reducer function accepts two parameters, the current state & the action object . So conventionally, the action is an object with one required property and one optional property:

  • type is the required property. It tells the reducer what piece of logic it should be using to modify the state.
  • payload is the optional property. It provides additional information to the reducer on how to modify the state.
const reducer = (state, action) => {
  switch (action.type) {
    case 'Increment':
      return {...state, count: state.count + 1};
    case 'Decrement':
      return {...state, count: state.count - 1};
    case 'Reset':
      return {...state, count: 0};
    default:
      return state;
  }
};
Enter fullscreen mode Exit fullscreen mode

Basically reducer accepts a current state, update the state on the basis of action object and return a new state.

Conclusion

We can conclude the useReducer in one picture.

Untitled Diagram.drawio.png

Let’s note down all the key points regarding the useReducer

  • useReducer is used for managing complex state.
  • useReducer accepts two argument reducer function and initial state for initialisation .

    useReducer(reducer, initialState)
    
  • We can initialise useReducer lazily by passing the init function as a third parameter

    useReducer(reducer, initialState,init)
    
  • useReducer returns an array, whose first item represent current state and other one is dispatch function.

    const [state, dispatch] = useReducer(reducer, initialState);
    // state and dispatch is just a naming convention.
    
  • We can update the state by calling dispatch method. It accepts an object with two parameter. One is type and other is payload for the additional information.

  • The reducer function accepts the current state and action object. On the basis of action.type it update the current state and return the new updated state.

Thanks for reading this article. Feel free to add suggestion. You can follow me on Twitter

Discussion (11)

Collapse
lukeshiru profile image
Luke Shiru

With modern JS you can go from this:

const reducer = (state, action) => {
    switch (action.type) {
        case "Increment":
            return { ...state, count: state.count + 1 };
        case "Decrement":
            return { ...state, count: state.count - 1 };
        case "Reset":
            return { ...state, count: 0 };
        default:
            return state;
    }
};
Enter fullscreen mode Exit fullscreen mode

To this:

const reducer = (state, { type }) =>
    ({
        Increment: { ...state, count: state.count + 1 },
        Decrement: { ...state, count: state.count - 1 },
        Reset: { ...state, count: 0 },
    }[type] ?? state);
Enter fullscreen mode Exit fullscreen mode

And if the reducer will only update the count, you could even do something like:

const reducer = ({ count, ...state }, { type }) => ({
    ...state,
    count:
        {
            Increment: count + 1,
            Decrement: count - 1,
            Reset: 0,
        }[type] ?? count,
});
Enter fullscreen mode Exit fullscreen mode

Cheers!

Collapse
martinpham profile image
Martin Pham

I wouldn’t call it modern JS (btw this thing existed I think 10 years ago, called Object literals), also it’s worse & they are totally different things:

The OP uses switch case which is perfect for this case, since JS will create a jump table which has better performance.

But yours will create object everytime repeatedly.

Collapse
lukeshiru profile image
Luke Shiru • Edited on

10 years of nullish coalescing and arrow functions? Sure! 🤣

Thread Thread
martinpham profile image
Martin Pham • Edited on

Before laughing, you didn't realize that I'm talking about the object literal vs switch-case, and explaining why your solution is worse?

Thread Thread
lukeshiru profile image
Luke Shiru

Let's make more clear why did I laugh:

I wouldn’t call it modern JS.

I used const, an arrow function, spread, destructuring, and nullish coalescing, but you don't call it modern JS because I used an object literal.

The OP uses switch case which is perfect for this case, since JS will create a jump table which has better performance.

But yours will create object everytime repeatedly.

Performance can be better as we talked in this comment, unless you have to run it like 2000 times, then the perf drops by 5%. But for me the most valuable thing about using an object in contrast with a switch/case, is that we reduce the "boilerplate" code a lot (similar to what happens when you go from a for to a Array.prototype.map, or when we go from a class to a function. Here's an image with the boilerplate grayed out, and what's significant in color ... maybe this way you see what I mean:

Screenshot comparing case code with object code

Thread Thread
martinpham profile image
Martin Pham • Edited on

1) First, it was very clear when I mentioned the main different thing between your solution and the OP's, is the switch vs object literal.
If you're still trying to say the main differences are the arrow function, spread, destructuring,.. things (which OP already used!) … I have nothing to say. It's a useless argument because you will always try to find a non-sense reason. Feel free to laugh anyway.

2)
a) In the OP's solution, there will be a jump table, and it will jump to the correct part to run (destructuring the state, merging with new count value)
b) In your solution, you will always create the object, plus, it will destructure the state + merge the count value 3 times (for Increment, Decrement, and Reset key).

Want to see performance? (Btw if you understand it well, you don't even need to benchmark it lol)

  • 1 dispatch: OP - 100%, Yours - 46%
  • 2 dispatches: OP - 100%, Yours - 42%
  • 5 dispatches: OP - 100%, Yours - 37%

Yours is even worse in the case in which the next state is null, nullish coalescing will make the next state = previous state instead.

Thread Thread
lukeshiru profile image
Luke Shiru • Edited on

Do you usually write all actions inline? In closer to real world scenarios, the perf difference is way smaller, and the code is way more maintainable.

My focus isn't performance, because obviously switch is faster in simple scenarios, like doing ~~value might be faster than Math.floor(value). My focus is maintenance, reuse and so on. And as I mentioned in the previous comment, the lack of boilerplate between the two approaches already makes the object approach better from my point of view.

And that thing you said at the end about the next state being null is odd to say the least. Why would you ever set the entire state to null? Even when you're one of those folks that consider null useful, setting the entire state to be null doesn't make much sense.

Is kinda pointless to keep debating this because in this (as in many other topics) we have different priorities and opinions, so the only thing we can agree on is to disagree.

Collapse
yadav_rajshekhar profile image
Rajshekhar Yadav Author

Thanks for sharing.

Collapse
martinpham profile image
Martin Pham

Nice article!
However I’d like to add some ideas:

  • Instead of returning state on switch’s default, you might want to throw an Error instead. When someone dispatches an unknown action type, it should notice him.
  • Instead of destructuring the state everytime, you might want to try immerjs which is very handy and safer
Collapse
yadav_rajshekhar profile image
Rajshekhar Yadav Author

Thanks for sharing.
I have stared learning react -native. Before this I was Working as an Android Application developer. Feel free to provide the resources. I love to read them.
By the way whatever you guys are discussing. I love to learn from it.
Thanks again

Collapse
martinpham profile image
Martin Pham

If you have Java background, I believe you wouldn’t have any problem learning js/ts and react.

Keep moving!