Hooks are reusable functions. They allow you to use state and other features (e.g. lifecycle methods and so on) without writing a class. Hook functions let us “hook into” the React state lifecycle using functional components, allowing us to manipulate the state of our functional components without needing to convert them to class components.
React introduced hooks back in version 16.8 and has been adding more ever since. Some are more used and popular than others, like useEffect
, useState
, and useContext
hooks. I have no doubt that you’ve reached for those if you work with React.
But what I’m interested in are the lesser-known React hooks. While all React hooks are interesting in their own way, there are five of them that I really want to show you because they may not pop up in your everyday work — or maybe they do and knowing them gives you some extra superpowers.
Table of Contents
useReducer
The useReducer
hook is a state management tool like other hooks. Specifically, it is an alternative to the useState
hook.
If you use the useReducer
hook to change two or more states (or actions), you won’t have to manipulate those states individually. The hook keeps track of all the states and collectively manages them. In other words: it manages and re-renders state changes. Unlike the useState
hook, useReducer
is easier when it comes to handling many states in complex projects.
Use cases
useReducer
can help reduce the complexity of working with multiple states. Use it when you find yourself needing to track multiple states collectively, as it allows you to treat state management and the rendering logic of a component as separate concerns.
Syntax
useReducer
accepts three arguments, one of which is optional:
- a reducer function
initialState
- an
init
function (optional)
const [state, dispatch] = useReducer(reducer, initialState)
const [state, dispatch] = useReducer(reducer, initialState initFunction) // in the case where you initialize with the optional 3rd argument
Example
The following example is an interface that contains a text input, counter, and button. Interacting with each element updates the state. Notice how useReducer
allows us to define multiple cases at once rather than setting them up individually.
import { useReducer } from 'react';
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'USER_INPUT':
return { ...state, userInput: action.payload };
case 'TOGGLE_COLOR':
return { ...state, color: !state.color };
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = useReducer(reducer, { count: 0, userInput: '', color: false })
return (
<main className="App, App-header" style={{ color: state.color ? '#000' : '#FF07FF'}}>
<input style={{margin: '2rem'}}
type="text"
value={state.userInput}
onChange={(e) => dispatch({ type: 'USER_INPUT', payload: e.target.value })}
/>
<br /><br />
<p style={{margin: '2rem'}} >{state.count}</p>
<section style={{margin: '2rem'}}>
<button onClick={(() => dispatch({ type: 'DECREMENT' }))}>-</button>
<button onClick={(() => dispatch({ type: 'INCREMENT' }))}>+</button>
<button onClick={(() => dispatch({ type: 'TOGGLE_COLOR' }))}>Color</button>
</section>
<br /><br />
<p style={{margin: '2rem'}}>{state.userInput}</p>
</main>
);
}
export default App;
From the code above, noticed how we are able to easily managed several states in the reducer (switch-case), this shows the benefit of the useReducer
. This is the power it gives when working in complex applications with multiple states.
useRef
The useRef
hook is used to create refs on elements in order to access the DOM. But more than that, it returns an object with a .current
property that can be used throughout a component’s entire lifecycle, allowing data to persist without causing a re-render. So, the useRef
value stays the same between renders; updating the reference does not trigger a re-render.
Use cases
Reach for the useRef
hook when you want to:
- Manipulate the DOM with stored mutable information.
- Access information from child components (nested elements).
- Set focus on an element.
It’s most useful when storing mutatable data in your app without causing a re-render.
Syntax
useRef
only accepts one argument, which is the initial value.
const newRefComponent = useRef(initialValue);
Example
Here I used the useRef
and useState
hook to show the amount of times an application renders an updated state when typing in a text input.
import './App.css'
function App() {
const [anyInput, setAnyInput] = useState(" ");
const showRender = useRef(0);
const randomInput = useRef();
const toggleChange = (e) => {
setAnyInput (e.target.value);
showRender.current++;
}
const focusRandomInput = () => {
randomInput.current.focus();
}
return (
<div className="App">
<input className="TextBox"
ref ={randomInput} type="text" value={anyInput} onChange={toggleChange}
/>
<h3>Amount Of Renders: {showRender.current}</h3>
<button onClick={focusRandomInput}>Click To Focus On Input </button>
</div>
);
}
export default App;
Notice how typing each character in the text field updates the app’s state, but never triggers a complete re-render.
useImperativeHandle
You know how a child component has access to call functions passed down to them from the parent component? Parents pass those down via props, but that transfer is “unidirectional” in the sense that the parent is unable to call a function that’s in the child.
Well, useImperativeHandle
makes it possible for a parent to access a child component’s functions.
How does that work?
- A function is defined in the child component.
- A
ref
is added in the parent. - We use
forwardRef
, allowing theref
that was defined to be passed to the child. useImperativeHandle
exposes the child’s functions via theref
.
Use cases
useImperativeHandle
works well when you want a parent component to be affected by changes in the child. So, things like a changed focus, incrementing and decrementing, and blurred elements may be situations where you find yourself reaching for this hook so the parent can be updated accordingly.
Syntax
useImperativeHandle (ref, createHandle, [dependencies])
Example
In this example, we have two buttons, one that’s in a parent component and one that’s in a child. Clicking on the parent button retrieves data from the child, allowing us to manipulate the parent component. It’s set up so that clicking the child button does not pass anything from the parent component to the child to help illustrate how we are passing things in the opposite direction.
// Parent component
import React, { useRef } from "react";
import ChildComponent from "./childComponent";
import './App.css';
function useImperativeHandle() {
const controlRef = useRef(null);
return (
onClick={
() => {
controlRef.current.controlPrint();
}
}
>
Parent Box
);
}
export default useImperativeHandle;
// Child component
import React, { forwardRef, useImperativeHandle, useState } from "react";
const ChildComponent = forwardRef((props, ref) => {
const [print, setPrint] = useState(false);
useImperativeHandle(ref, () => ({
controlPrint()
{ setPrint(!print); },
})
);
return (
<>
Child Box
{ print && I am from the child component }
);
});
export default ChildComponent;
Output
useMemo
useMemo
is one of the least-used but most interesting React hooks. It can improve performance and decrease latency, particularly on large computations in your app. How so? Every time a component’s state updates and components re-render, the useMemo
hook prevents React from having to recalculate values.
You see, functions respond to state changes. The useMemo
hook takes a function and returns the return value of that function. It caches that value to prevent spending additional effort re-rendering it, then returns it when one of the dependencies has changed.
This process is called memoization and it’s what helps to boost performance by remembering the value from a previous request so it can be used again without repeating all that math.
Use cases
The best use cases are going to be any time you’re working with heavy calculations where you want to store the value and use it on subsequent state changes. It can be a nice performance win, but using it too much can have the exact opposite effect by hogging your app’s memory.
Syntax
useMemo( () =>
{ // Code goes here },
[]
)
Example
When clicking the button, this mini-program indicates when a number is even or odd, then squares the value. I added lots of zeros to the loop to increase its computation power. It returns the value in spilt seconds and still works well due to the useMemo
hook.
// UseMemo.js
import React, { useState, useMemo } from 'react'
function Memo() {
const [memoOne, setMemoOne] = useState(0);
const incrementMemoOne = () => { setMemoOne(memoOne + 1) }
const isEven = useMemo(() => {
let i = 0 while (i < 2000000000) i++ return memoOne % 2 === 0
},
[memoOne]);
const square = useMemo(()=> {
console.log("squared the number"); for(var i=0; i < 200000000; i++);
return memoOne * memoOne;
},
[memoOne]);
return (
Memo One -
{ memoOne }
{ isEven ? 'Even' : 'Odd' } { square }
);
}
export default Memo
Output
useMemo
is a little like the useCallback
hook, but the difference is that useMemo
can store a memorized value from a function, where useCallback
stores the memorized function itself.
useCallback
The useCallback
hook is another interesting one and the last section was sort of a spoiler alert for what it does.
As we just saw, useCallback
works like the useMemo
hook in that they both use memoization to cache something for later use. While useMemo
stores a function’s calculation as a cached value, useCallback
stores and returns a function.
Use cases
Like useMemo, useCallback
is a nice performance optimization in that it stores and returns a memoized callback and any of its dependencies without a re-render.
Syntax
const getMemoizedCallback = useCallback (
() => { doSomething () }, []
);
Example
{ useCallback, useState } from "react";
import CallbackChild from "./UseCallback-Child";
import "./App.css"
export default function App() {
const [toggle, setToggle] = useState(false);
const [data, setData] = useState("I am a data that would not change at every render, thanks to the useCallback");
const returnFunction = useCallback(
(name) =>
{ return data + name; }, [data]
);
return (
onClick={() => {
setToggle(!toggle);
}}
>
{" "}
// Click To Toggle
{ toggle && h1. Toggling me no longer affects any function }
);
}
// The Child component
import React, { useEffect } from "react";
function CallbackChild(
{ returnFunction }
) {
useEffect(() =>
{ console.log("FUNCTION WAS CALLED"); },
[returnFunction]);
return { returnFunction(" Hook!") };
}
export default CallbackChild;
Output
Final thoughts
There we go! We just looked at five super handy React hooks that I think often go overlooked. As with many roundups like this, we’re merely scratching the surface of these hooks. They each have their own nuances and considerations to take into account when you use them. But hopefully you have a nice high-level idea of what they are and when they might be a better fit than another hook you might reach for more often.
The best way to fully understand them is by practice. So I encourage you to practice using these hooks in your application for better understanding. For that, you can get way more in depth by checking out the following resources:
- Intro to React Hooks (Kingsley Silas)
- Hooks at a Glance (React documentation)
- Hooks Cheatsheet (Ohans Emmanuel)
- The Circle of a React Lifecycle (Kingsley Silas)
- Hooks of React Router (Agney Menon)
- Testing React Hooks With Enzyme and React Testing Library (Kingsley Silas)
Thanks for writing these up! There are new official React docs that teach React Hooks-first at beta.reactjs.org. Check them out!
Thank you Rachel. Will do!
The React documentation says that useMemo is only for optimization and does not guarantee to never recompute.
How would you implement it when you need it to not recompute (for example if it uses random values)? Would you use
useRef
? Or perhapsuseState
‘s initial value (even though it’s not a state)? I couldn’t find a natural method.In the useRef example what do you mean by complete re-render? With each input no of re renders is increasing in the example so how is it different?
Thanks for sharing! I think I need to refactor a file in my work codebase to make use of the useReducer hook, cause the state right now is a lot
One question, with the useImperativeHandle hook, I noticed the
ref
passed to the child component doesn’t get added as an attribute to any of the child component elements.So with the
forwardRef
func, does it mean that every of the child component elements is being referenced through the ref hook on the parent component or it’s just the functioncontrolPrint()
added touseImperativeHandle
hook that is being referenced?Thanks for sharing the article.
Although i’m not sure the useMemo code section example is actually implemented as intended to demonstrate that the useMemo callbacks (x2) are not to be recomputed, and should instead return cached values.
You see, as useMemo in both cases is dependent on changes to the “count” variable in your current code example, the “count” variable is always updated on each click on the “Memo One” button (via “setCount()”, and hence the callbacks within the useMemo functions are always going to be re-computed on each click.
Wouldn’t this defeat the purpose of trying to demonstrate that useMemo should return the cached value, rather than continuously re-compute on each click?
Or am i misunderstanding something?
I noticed a typing error in my original reply.. so rather than:
“count” and “setCount()”, i mean “memoOne” and “setMemoOne()”
can an admin or user tell me how the display code in WordPress in CSS tricks, I mean it is vs any plugin or directly vs any PHP code? please
Chris has a post on it right here: https://css-tricks.com/posting-code-blocks-wordpress-site/