Additional Hooks in React - useReducer()

Additional Hooks in React - useReducer()

This blog post continues the series about React Hooks.

It covers one of additional hooks in React - useReducer().

What is useReducer()?

useReducer() is JavaScript function, which allows to use reducer functions from state management in functional component. It is an alternative to useState() hook and as react developers say : "New one and improved" :)

Before you read any further, you should be familiar with state management and reducers.

How and when use useReducer()

To use this hook, you need to import it first from React library like this:

import {useReducer} from 'react';

You can use this hook without import as well like this - React.useReducer(), for me it's just more convenient to import and destructure first this function and then use it when need in the code.

Afterwards we can set up this hook like this:

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

Let's examine this example more detailed.

NOTE: If you are familiar with Redux, you already know how it works

useReducer hook :

  • accepts reducer function and initial state.
  • returns current state and dispatch method

Let's consider the following example - we are building an app which is a simulation of the farm.

Here’s a store of data, where we have four hungry animals (this is our initialState):

const animals = [
  {type: 'horse', isHungry: true},
  {type: 'sheep', isHungry: true},
  {type: 'cow', isHungry: true},
  {type: 'pig', isHungry: true}
]

Let’s create a reducer to feed them:

const animalsReducer = (animals, action) => {

  if(action.type == 'feed') {
    return animals.map(animal => {
      if(animal.type == action.specificAnimal) {
        animal.isHungry = false;
      }
      return animal;
    })
  }
  if(action.type == 'giveWater') {
    return animals.map(animal => {
      if(animal.type == action.specificAnimal) {
        animal.isHungry = true;
      }
      return animal;
    })
  }
}

So, the important thing to notice here is what we pass to our animalsReducer function: our initial state (animals) and action (you could think of this as a setState method). The action.type identifies what type of action we want to do and action.specificAnimal identifies for us which animal we want to perform the action on.

Now that we have our reducer and store setup, let’s implement them with our useReducer() hook:

const [state, dispatch] = useReducer(animalsReducer, animals);

And finally let's implement our dispatch functions and use them in JSX:

const feed =(animalType)=>{dispatch({ type: 'feed', specificAnimal: animalType });}

const giveWater = (animalType) => {dispatch({ type: 'giveWater', specificAnimal: animalType });}

return (
  <div>
    {state.map((animal, idx) => (
      <div key={idx} style={{ display: 'flex', width: '50%', justifyContent: 'space-around' }}>
        <div>{animal.type}</div>
        {animal.isHungry ?
          <div>NOT HUNGRY! <button onClick={() => feed(animal.type)}> feed </button> </div> :
          <div>HUNGRY<button onClick={() => giveWater(animal.type)}> give water</button> </div>}
      </div>
    ))}
  </div>
)

The dispatch method receives an object that represents the action we desire to be done. We pass our action to our reducer through the useReducer. Our reducer returns updated state.

CONCLUSION

You can use useState in the same component or hook that's using useReducer and you can have multiple useStates and multiple useReducers in a single hook or component. useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

Thank you for reading my blog. Feel free to connect on LinkedIn or Twitter :)

Buy Me a Coffee at ko-fi.com