useState, useEffect, useCallback, useMemo, and more: React Hooks explained with examples

Support us on Patreon

Photo by Anthony from Pexels

Hooks are a great addition to React, the introduction of hooks reduced the learning curve for the framework, being a little piece of code added to your component, that allows you to interact with the component state and lifecycle.

As it is stated on the React documentation, hooks were made to allow the following to become possible:

  • Reuse stateful logic
  • Make components less complex
  • Allow the use of more React features inside functional components

You can think about React hooks as a set of tools, and this is a basic overview of what you have available:

  • Want to store data that is important to the visual aspect of your component? useState
  • Want to do something that depends on the component lifecycle? useEffect
  • Want to "cache" the result of a very expensive computation? useMemo
  • Want to "cache" a function itself? useCallback
  • Want to store data that is not important to the visual aspect of the component? useRef
  • Want to provide global values to children of an element? useContext

Other hooks also provide some extra functionality: useReducer, useImperativeHandle, useLayoutEffect, and useDebugValue, but they are not on the focus of this article.

Managing component state with useState

The useState hook is meant to be used when you have data stored in your component, which is relevant to its rendering. But what this means?

The data inside your component can be either relevant or irrelevant to its visual aspect.

The below code shows data relevant to the component rendering:

See the Pen by plaguer (@plaguer) on CodePen.

Every time the button is clicked, setColor is called, this function will update the state and trigger rendering again, if we were to store and change the data differently, the component wouldn't rerender to show us the new updated color.

The example uses this declaration using useState:

const [ color, setColor ] = React.useState('red');

useState is a function that will return an array, by destructuring the array-like above, we can get the actual value from the first element, and the function to update the state as the second one. No magic is happening there, we are just naming two variables from the array return with appropriate names, the order matters, the names not so much, although following the convention above is great.

Now let's see how we are using these two functions.

setColors is being used inside a function we declare:

setColor(c => c === 'red' ? 'blue' : 'red')

As we are passing a function to setColor, we can use its latest value, we cannot trust the value returned on the array because it may not be updated, the function we pass to this setting function will be called with the updated value for our state.

Sometimes you don't care about the previous value of your state to change it, in that case, you could just pass the new value, like so:

setColor('red')

And as for the color state value itself, we use it inside our render:

<div style={{background: color, height: '100vh', width: '100vw'}}>

We use it for our background color, and as it is being used inside our component return, which means it is used for rendering/displaying the component, this data must be used along with React.useState, and it is what we did!

Using useEffect when the component lifecycle matters

The main body of a React functional component, the render phase, is not appropriate for several types of code.

The body of a component is called every time your component is rendered, and this can lead to chaos, especially if what you're doing in your component involves side effects, for example, requesting data from an API.

The appropriate way to handle side effects in your component is by using the useEffect hook, it lets you be aware of the lifecycle of your component.

To help you understand why we need useEffect, take a look at the following example:

CodePen URL

Take a look at the code, what is it doing, what do we expect from it?

  • Declare we are using a state by using useState, the variable is count, and we can update it with setCount. The initial value of count is 0
  • Declare a function called increment, which adds 1 to count
  • Call setTimeout, to run the increment function in around one second
  • Render a text with our count variable in it

What did we expect?

  • If we are using setTimeout, instead of setInterval, our component should initially be rendered with a value of 0
  • After 1 second, our component will rerender and show the value of 1
  • That's it?

If you take a look at the preview of our code, you will notice that it never stops incrementing, it keeps going, 0... 1... 2..., but we only called increment once!

By putting our setTimeout function inside the component body, we created a nightmare.

The increment function that is called after 1 second, will further call the setCount function, and setCount is from useState, it makes our component render again after changing the state.

We can solve this!

CodePen URL

useEffect is used with two arguments, a function, and an array(optionally).

The function we give useEffect runs after every render unless we supply the second argument, the array.

The array we pass useEffect will tell the hook when to run the code again, it will run when any of the variables in that array change their value. We are passing an empty array, so it will never run this code again, on this component lifecycle.

There is one single problem with that usage, we created a timeout inside useEffect, this timeout outlives our component, and this cannot happen, everything that we define on timeout must be cleaned if our component leaves the screen.

CodePen URL

Fortunately, clearing every side effect created inside useEffect is an easy task, you can optionally return a function inside your useEffect code, this function will be used to clean everything that was previously created.

The above code uses clearTimeout, effectively stopping the timeout if it is needed.

Using useRef to hold data irrelevant to the component appearance

useRef doesn't trigger the rendering of our component, if we are storing a value that doesn't need the component to render again, why would we unnecessarily make it render using useState?

CodePen URL

The above example shows useRef being used, we only care about rendering the component again once we want to display it, by pressing the button.

We are storing the input element inside the ref, and once the button is pressed, we check its value and update the state, which will only then reflect our change in the screen.

Somehow useRef is often feared by React developers, but it is a powerful hook when used the right way.

Using useMemo to memoize values

Sometimes we have to do intensive calculations, that can interrupt UI, freeze, and cause serious problems, there are ways to minimize their damage and avoid all that, but still... they take time to finish.

CodePen URL

Check the above component, you will notice that your browser freezes just by opening that - depending on your hardware - we are doing a useless and expensive computation there, every time our component renders.

We defined a custom hook and we use it to declare a rerender function, calling that function forces our component to rerender, do not worry about its implementation.

We call our rerender function inside useEffect, using the value from the select box as a dependency, this effectively makes our component rerender twice after changing the selected value.

When the calculation happens, there is a console.log that says "Something expensive is rendering..." when it runs.

To understand what happens with this component, do the following:

  • Open the dev tools by pressing F12, and go to the console tab.
  • Change the select box, it will freeze the screen again, but pay attention to the console
  • The console logs "Something expensive is rendering..." twice! Every time we change the select, our component renders twice, and we do the calculation twice

Now let's simply wrap our bigCalc function around useMemo:

CodePen URL

Do the same as above, but notice the expensive calculation only happens once after changing the selection.

It still does the calculation once, because of the useMemo dependency, in this situation the value from the select input, changed, but besides changing the select, we won't do the calculation again.

Using useCallback to memoize functions

The useCallback hook has a weird name! But don't worry, it doesn't introduce some new weird asynchronous reactive multithread super promise function, it does almost the same as useMemo!

Quoting the React documentation:

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps)

So we are memoizing the function as the value we would pass to useMemo.

When you have a parent component of a heavy component that requires a function as a prop, you can use useCallback to memoize the function and avoid unnecessary renders on the heavy component.

That works because without useCallback or useMemo(returning a function), the function is redeclared every time, and for the child component, it is a new object being passed, when we memoize it, we can give the same object, so the child can compare and realize there is no need to rerender, the same props were given.

Beware of useMemo and useCallback

These hooks can improve your application performance, but there is no way to prove any real performance gain, rather than testing both methods. Do not preemptively use useMemo or useCallback for everything, with no measure.

Managing global values with useContext

The useContext hook allows you to consume a context using hooks.

CodePen URL

The above code creates a context with some styling properties, using useContext with the context we created, you can consume it, using the values provided.

Learn more

Hopefully, if you're new to React Hooks you were able to learn something useful here! Any inquiries about this article? Contact us!

More resources:

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Learn React.js by example: Building an Axolotl facts app, a 2021 guide

Scary indie horror games to play on August 2021