8 more React hooks you need to know about
Jan 20, 2024
By Matthew Tyson
Software Architect, InfoWorld |
Here’s a look at eight useful React hooks you may not know about, and how to use them.
Everyone knows about useState because it replaces an essential feature of class-based components—the member variables to hold state—with a functional equivalent. The useReducer hook does something similar, but for more complex scenarios where state transitions are more involved and the application benefits from making transitions explicit. The useReducer hook is inspired by the reducers found in Redux. It can be seen as a middle ground between the simplicity of useState and the complexity of a state management system like Redux.
Here’s an example of how to work with the useReducer hook. You can also see the reducer live in this JSFiddle.
The purpose of this example is to take the text from the input box and let the user click buttons to display the text in all uppercase or all lowercase. The code declares a new reducer with const [state, dispatch] = useReducer(reducer, initialState);. The useReducer takes the reducer function and the initialstate and returns an array, which we then destructure to state and dispatch variables.
The reducer itself is defined with: const reducer = (state, action) =>, giving a two-argument function. Whenever the dispatch function is called in the code, it will pass the current state along with an action object. In this case, the action object has a type field and we use that to determine how to mutate the state.
In a moderately complex application, useReducer can be helpful in managing complexity, and can even be shared across the application using the context. When useState is difficult to manage because of the complexity of the application, the useReducer hook can help.
The useCallback hook is a performance hook. It takes a function and ensures that only a single version will be returned and reused for all callers. If the function is expensive and called repeatedly by a loop or child components, the useCallback hook can net significant performance gains. This kind of optimization is known as memoizing a function.
In Listing 2, we have an example of using useCallback to use the same function across many items in a list. Here's the example in a live JSFiddle.
We use React.useCallback() to create a new memoized function at incrementCounter. We can use the memoized function as a normal function in the onClick handler, in the list. useCallback() takes a function as a first argument. Within that function, we can perform any work we need. The key difference is that React simply returns the cached value of the function unless something has changed in the list of dependency variables, which in our example is the counter variable.
This is a precious magic power in cases where you need to share an expensive function among several callers, especially child components. Bear in mind as we look at the next hook (useMemo) that useCallback stashes the function itself. That is to say, useCallback prevents the actual function from being recreated each time it appears, and only recreates it when necessary.
The useMemo hook is useCallback’s sibling. Where useCallback caches the function, useMemo caches the function return value. It's a subtle distinction, but important.
When should you use useMemo versus useCallback? The answer is: use useMemo when you can, and useCallback when you have to. The useCallback hook is merited when the performance hit you are avoiding is the creation of the function itself in the rendering, while useMemo will not prevent the function from being recreated wherever it appears. However, useMemo will ensure the function returns a cached value if the dependencies have not changed.
Listing 3 shows useMemo in an example. ,You can also see this example in a live JSFiddle.
In this example, we have a function that costs a lot to compute: computeExpensiveValue. It depends on a single input, count. We can use computeExpensiveValue(count), [count]) to tell react: only run this function if the count has changed; otherwise, return the cached computation.
Again, the difference with useCallback is not obvious. The important distinction is: useCallback is the hook to use when the function itself is being repeatedly instantiated, incurring a performance hit. Otherwise, useMemo is the better choice.
In React, the context is a variable scope that exists outside of the components and to which all components have access. As such, it is a quick and easy global space for application-wide data. For complex scenarios, it might be better to use an official data store like Redux, but for many uses, context will suffice. The useContext hook is how functional components interact with the context.
In Listing 4, we have two components, Speak and Happy, that are used by the application parent component, App. The user can toggle between a dog and a cat state, and via the global context, the child components will reflect the choice (wagging tail versus purring, for example). You can also check out the live JSFiddle for this example.
The useRef hook lets you manage a reference outside the render cycle. useState causes the React engine to render when it changes, whereas useRef does not. The useRef hook is like a special area off to the side of React that says: This variable is special and it isn’t part of the reactive UI. The most common use case for useRef is to gain access directly to the DOM and its API. Normally, in reactive thinking, this is avoided and everything should be done through the reactive engine, but sometimes it’s unavoidable.
In Listing 5, when the button is clicked on, we use the useRef hook to hold a reference to the input field and use the DOM methods to put the focus on it and set its value to “Something amazing." Here's the JSFiddle for the useRef example.
useEffect is the second most common hook after useState. It is frequently used to make API calls, change the DOM, or take other action (that is, cause an effect) when something changes in the component state. In a sense, useEffect lets you define a reactive variable or variables and the behavior that will occur for them.
In Listing 6, we define a drop-down list to select a Star Wars character. When this value changes, we make an API call to the Star Wars API (SWAPI) and display the character’s data. Here's the live JSFiddle for this example.
useLayoutEffect is a lesser known hook that comes into play when you need to make measurements of the rendered DOM. The useLayoutEffect hook is called after React draws the UI, so you can count on it giving access to the actual layout.
In Listing 7, we use useRef to grab an element off the DOM and useLayoutEffect to be notified when the rendering is complete. Then, we calculate the element size using the DOM API. Also see the JSFiddle for this example.
Sometimes, you need to get the reference directly to a component. You can do this with useRef, and if you also want to provide access to the component's DOM, you can use forwardRef. Sometimes, though, you need to customize the behavior that the component exposes via the reference. For that, you need the useImperativeHandle hook.