React Hooks: useReducer, useCallback, & useMemo

Looking for a great (remote only) React Dev? Or just feel like having a chat? Visit my profile on LinkedIn and say hi! 😃

React: Additional Hooks

Welcome to my new series on React Hooks. Over the next couple of articles, we’ll be going over Reacts Additional Hooks (not useState and useEffect)

useReducer

useReducer draws it’s inspiration from Redux’s reducer pattern.

useReducer takes three positional arguments — a reducer, its initial state, and a function that can return the initial state — let’s call it init for now. The final argument is optional. You can just pass in the initialState as is (e.g. {count: 0}), but if you want to operate on it in any way, I’d suggest using the init function.

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

The init function will automatically receive initialState as an argument.

const init = initialState => return { count: initialState.count + 1}

useReducer returns state and a dispatch function. You can pass the dispatch function down to other components which should call it with a type. Calling the property type is just a naming convention, but most people use it.

<button onClick={() => dispatch({type: "increment"})>Inc</button>

This type is then caught by the reducer.

const reducer = (state, action) => {
if (action.thing === INCREMENT) return { count: state.count + 1 };
throw "ERROR - a valid type was not provided in your dispatch"
};

If the reducer just returns the current state unchanged, React will not execute a re-render.

When Should We Employ useReducer

useReducer is best used when you are dealing with a complex object where individual properties need to be operated on.

For example, if your app pulls in a complex data object like this, and you need to update different properties within it, you should use useReducer.

const data = {
author: "Ben Grunfeld",
age: 40,
posts: [
{
title: "My First Post",
datePublished: "20-7-2020",
content: "something smart",
},
{
title: "My Second Post",
datePublished: "23-7-2020",
content: "something witty",
},
]
}

useCallback

useCallback stops a function from being re-initialized if a specified dependency hasn’t changed. It is mostly used in conjunction with React.memo, where a child component takes the function as a prop.

The function (defined in the parent) will be reinitialized on every render of the parent component if it is not memoized, and as a consequence, React.memo will see it as if the prop has changed and will in turn re-render the child component. This causes unnecessary re-renders, which may be quite expensive, depending on the complexity of the component.

Here is an example of an unnecessary re-render caused by a non-memoized function that is a prop:

If you run this code, you’ll notice that the console.log statement gets executed every time you click on the button. This shows us that the CountButton component is getting re-rendered every time, which is completely unnecessary.

In this example case, it quite cheap so the optimization is likely excessive, but what if that component was more expensive to re-render? What if there were many such components? Then you’d have a potentially significant slowdown in the app!

So how do we solve this conundrum?

The solution is that we use React.memo on the CountButton component, but React.memo only avoids a re-render if its props haven’t changed.

Since updateCount keeps getting re-initialized in the parent, the prop will look different to React.memo, so it will re-render. But if we use useCallback on updateCount, then it will ONLY re-initialize the function if the specified dependencies change.

Here is a complete code example. The way you can tell that CountButton is not re-rendering is that the console.log statement doesn’t execute every time you click the button now.

A HUGE thanks to Ben Awad for explaining much of the above in his fantastic video.

useMemo

useMemo is similar to useCallback except that instead of memoizing a function, it memoizes a value.

Imagine we have to process some data that we receive from an API that does not change between renders. In our example blow, we’ll just mock the data for simplicity sake.

In the example above, every time we click the Increment button, the parent component re-renders, which you can see via the console.log statement, and the function which processes the data is run again — which you can also see via it’s own console.log statement.

Like we said in the useCallback section, this could become quite expensive if the function itself is significantly complex enough, or if there are many such processing steps at multiple places in the app.

Since the data doesn’t change between renders, the output of the processing function will not change, so there’s not point in repeating it every single time.

This is where we use useMemo, which will save the value to the cache, and will NOT run again during re-renders unless the value of data is changed.

If you run the above code, you will see that every time you click on the Increment button, the parent App will re-render, but now you’ll see that double does not execute on each click (no console.log statement is getting executed), because its output has been memoized until data gets changed.

Conclusion

React’s additional hooks enable you to vastly improve the performance of your app by avoiding unnecessary re-renders, and also by avoiding pointlessly running expensive functions whose output will always be the same multiple times.

They also give you an easier way to work with complex objects in your state.

Happy Coding! 😃

I’m a Front End Engineer who loves React, NextJS, and GraphQL. Need a REMOTE React Developer? Contact me at: https://www.linkedin.com/in/bengrunfeld/

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store