React hooks: how to use useState and useReducer effectively?

Mohcine NAZRHAN
7 min readJul 22, 2019

--

Photo by Brook Anderson on Unsplash

In this article we will discover two hooks, the basic one useState, and the advanced one useReducer, both can do the same thing which is state management/manipulation but in a different way for different use cases.

Before we dig into each one, let me quickly recap on React hooks.

What is React Hooks?

They are collectively called hooks, they are special functions that add state, lifecycle feature, performance optimization, and more to your functional components.

They are 10 built-in functions that have their unique name all begin with a three-letter ‘use’, but with different functionality and use cases, they are completely optional you don’t have to re-write your existing class but you can start using them in the new ones since react is all about incremental adoption.

React Hooks are just functions that take an argument and return value, in addition too, you can create your own ones easily as a custom hook.

Before using them you have to be aware of the rules of using these hooks.

We have to respect these rules otherwise we will get confusion and unexpected result, since React relies on the order in which Hooks are called, the order of the Hook calls must be the same on every render.

React won’t natively throw error or warning to let you know, you have to add a linter plugin that ensures and enforce these rules automatically within your projects.

There are only two rules to follow.

1. Only Call Hooks at the Top Level i.e. not within conditionals, loops, or nested functions.

2. Only Call Hooks from React Functions i.e. Functional Components and Custom Hooks.

Now, let’s go quickly through some of them.

Starting with the one you will use the most

  • useState: This hook gives us the ability to make functional components have their local state through a very simple API as you can see in the example below.
  • useReducer: This hook gives us an easy way to organize our state management, if you are already familiar with Redux you will find this easy to understand and work with.
  • useEffect: This hook has been made for side effects, it’s a combination of componentDidMount , componentDidUpdate , and componentWillUnmount all in one place.
  • useContext: This gives us the ability to pass data, config data in our app, down the components tree without using prop drilling or a third-party library like redux.
  • useCallback: Gives us a nice performance gain by memorizing the given callback and update it when the given dependencies change.
  • useMemo: This hook cache some data, saving some compute time on our app and hopefully making it more responsive in the process.

I encourage you to discover the others in the official documentation.

It’s time to learn more about useState and use reducer and see them in practice.

The useState hook

Let’s consider that we have a functional component that represents our app footer like this:

As you can see it’s a very simple component, at some point, we need a local state to handle show/hide some extra information by clicking on a button, and as you know, the functional component is stateless, and we can only have state in class components.

Before react hooks we should convert this component to a class component so that we can have a state capability, but now with react hooks and especially useState we can have a local state in our functional component.

This hook work super straightforward, easy to think about, and much cleaner than using a complex object with setState.

Let’s hook into our functional component the useState to have a local state and handle show/hide our extra footer information.

Start by importing useState from React then call useState with a default value, this will return two values in the form of an array, which we have simplified by deconstructing it into two variables, you can think of these two variables as getters and setters in OOP, use extraInfo to get the current state and setExtarInfo to set a new value, extraInfo is just a variable while setExtraInfo is a function that receives a new state, either a value or a function that returns a value.

We used the extraInfo to show or hide the paragraph according to extraInfo’s current state, and use setExtarInfo to update the extraInfo’s current state.

You can name these variables whatever you want, but it’s a convention for the second variable to use ‘set’ as a prefix with a camel case.

It’s absolutely possible to use an object with useState instead of multiple useState, but the React team recommends doing multiple useState calls since it’s pretty easy and handy to deal with variables than an object.

If you end up having a lot of variables you can use a custom hook (we will talk about it in a separate article) to put them into one state or use useReducer which we will discover next.

Note unlike setState, the useState actually replaces the old state with the new one while setState merge them together.

Now let’s discover the useState’s big brother.

The useReducer hook

This hook gives us an easy way to organize our state management.

But first, a quick definition of what reducer means in the context of a React app.

A reducer is simply a function, a pure function that takes the previous state as the first parameter and action as the second parameter, and returns a new state based on the act passed.

This hook is like useState but with additions features, you can think of this as a superset of useState,

If you are already familiar with Redux you will find this easy to use.

When we have a complex state logic that set different value according to the action needed, the useReducer is available to do this for you with a familiar approach that of Redux, and as the doc said: “useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.”

Let’s see it in practice.

Consider that we have a functional component for login users, which has its local state using the useState hook, and as you can see we have several useState, at this point we are perfectly adopting the recommendation of the React team to make multiple useState.

The source of this code is from hswolff.

Have you noticed that our states are scattered over the component giving us a bad experience trying to figure out the logic, add or change something?

Of course, you have noticed that.

This can be hard to scale and maintain in the future since we have to deal individually with each state.

Yes, it’s explicit, actually, it’s too explicit which eliminates the big picture level of the logic, we can’t get clearly what going on in the first look, just look and think about it.

Hemm well; can we group these states since they are related to each other? so we can give an intuitive name to each according to its action like ‘login’, ‘logout’, ‘success’, ‘failed’, and ‘typing’…?

Yes, we can, this is when the useReducer shines to make the state management more intuitive.

Let’s do it now using the useReducer.

The source of this code is from hswolff.

Look how awesome useReducer is, now our code is getting cleaner, easier to co-locate, and understand.

We separate the state management from the component to make it more readable, for example, if we want to change the default values, we have one place to deal with witch is initialState, if we want to change something about the state we can go directly to loginReducer…

If you are not familiar with Redux let me explain this to you.

By invoking useReducer we have to provide the reducer as a first argument, it’s just a function that will handle the state according to the action passed to, and the initial state as a second argument which hasn’t to be an object, it can be any data type.

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

useReducer is similar to useState, it gives us also two variables, the first holds the current state, and the second `dispatch` is a function that sends an action definition to the reducer to produce a new state depending on the type and other properties of the action.

The action definition object can have as many properties as needed, for example in this case we had to pass in addition to the type and payload the fieldName.

dispatch({ type: ‘field’, fieldName: ‘password’, payload: e.currentTarget.value, })

Like useState, useReducer replace the previous one with the new state, so be aware of that when using this hook, create a new object containing the previous state ‘… state’ followed by the ‘key: value’ properties that we want to modify.

return { …state, [action.fieldName]: action.payload, };

Remember that: state, dispatch, reducer, initialState are just names, you can name them as you want but it’s better to stick to the conventions.

Note: your reducer should NEVER make an HTTP request! keep that function pure and side-effect-free.

So, when should I use one and not the other?

It’s up to you and your need when to use either useState or useReducer.

Here is my suggestion for you, use useState when dealing with a property state that is unrelated or dependent on another, and use useReducer when you have multiple properties that you want to update differently based on actions, and also when one property in your state relies on the value of another.

Kent C. Dodds

Any time you need state x to update state y, that’s an insta-useReducer use case

Conclusion

To recap what we’ve learned, hooks have come to solve some very common problems that everyone uses React come across, they encourage the use of functional components to benefit from its simplicity and efficiency.

useState and useReducer provide a powerful approach to managing state in functional component for all cases, they are easy to use and think about.

--

--

Mohcine NAZRHAN

Mobile Web Specialist | Full-Stack Dev with a Front-end Focus