useState is the most well-known hook for using functional components in React, but it's just a start. Here are eight more React hooks, and how to use them. Credit: Curaga / Cofotoisme / Getty Images React remains the pacesetter among JavaScript UI frameworks. There are plenty of ongoing developments in React, but the most important shift of the last few years was the move to functional components. Functional components rely on hooks for many of their capabilities. The most common hook is useState, but there are many others. Here’s a look at eight useful React hooks you may not know about, and how to use them. useReducer 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. Listing 1. Simple useReducer example const {useReducer, Fragment} = React; const initialState = { text: "", isUpperCase: false, }; const reducer = (state, action) => { switch (action.type) { case 'SET_TEXT': return { ...state, text: action.text, }; case 'TOGGLE_UPPER_CASE': return { ...state, text: state.text.toUpperCase(), }; case 'LOWERCASE': return { ...state, text: state.text.toLowerCase(), }; default: return state; } }; const App = () => { const [state, dispatch] = useReducer(reducer, initialState); const handleChange = (event) => { dispatch({ type: 'SET_TEXT', text: event.target.value }); }; const handleToggleUpperCase = () => { dispatch({ type: 'TOGGLE_UPPER_CASE' }); }; const handleLowerCase = () => { dispatch({ type: 'LOWERCASE' }); }; return ( <div> <input type="text" value={state.text} onChange={handleChange} /> <button onClick={handleToggleUpperCase}>Toggle Upper Case</button> <button onClick={handleLowerCase}>Toggle Lower Case</button> {state.text} </div> ); }; 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. useCallback 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. Listing 2. useCallback in a iterated list const List = ({ items }) => { const [counter, setCounter] = React.useState(0); const incrementCounter = React.useCallback(() => { setCounter(counter + 1); }, [counter]); return ( <ul> {items.map((item) => ( <li key={item.id}> {item} - {counter} <button onClick={incrementCounter}>Increment</button> </li> ))} </ul> ); }; 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. useMemo 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. Listing 3. useMemo const { useMemo, useState } = React; const ExpensiveComputationComponent = () => { const [count, setCount] = useState(0); const computeExpensiveValue = (count) => { let result = 0; for (let i = 0; i < count * 10000000; i++) { result += i; } return result; }; const memoizedValue = useMemo(() => computeExpensiveValue(count), [count]); return ( <div> Count: {count} Expensive Value: {memoizedValue} <button onClick={() => setCount(count + 1)}>Increment Count</button> </div> ); }; 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. useContext 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. Listing 4. useContext in action const { createContext, useContext, useState } = React; const AnimalContext = createContext(); const Speak = () => { const { animalType } = useContext(AnimalContext); return ( <div> {animalType === 'dog' ? 'woof' : 'meow'} </div> ); }; const Happy = () => { const { animalType } = useContext(AnimalContext); return ( <div> {animalType === 'dog' ? 'wag tail' : 'purr'} </div> ); }; const App = () => { const [animalType, setAnimalType] = useState('dog'); const toggleAnimalType = () => { setAnimalType(prevAnimalType => (prevAnimalType === 'dog' ? 'cat' : 'dog')); }; return ( <div> <button onClick={toggleAnimalType}>Toggle animal type</button> <div>{animalType}</div> <AnimalContext.Provider value={{ animalType }}> <Speak /> <Happy /> </AnimalContext.Provider> </div> ); }; useRef 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. Listing 5. Simple useRef example const App = () => { const inputRef = React.useRef(null); const handleClick = () => { inputRef.current.focus(); inputRef.current.value="Something amazing"; }; return ( <div> <input ref={inputRef} type="text" /> <button onClick={handleClick}>Focus Input</button> </div> ); }; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <App/> ); useEffect 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. Listing 6. useEffect with API call const { useState, useEffect } = React; const StarWarsCharacter = () => { const [character, setCharacter] = useState('Luke Skywalker'); const [data, setData] = useState(null); useEffect(() => { const fetchData = async () => { try { const response = await fetch(`https://swapi.dev/api/people/?search=${character}`); const jsonData = await response.json(); setData(jsonData.results[0]); } catch (error) { console.error('Error fetching data:', error); } }; fetchData(); }, [character]); const handleCharacterChange = (event) => { setCharacter(event.target.value); }; return ( <div> <h1>Star Wars Character Details</h1> <select value={character} onChange={handleCharacterChange}> <option value="Luke Skywalker">Luke Skywalker</option> <option value="Han Solo">Han Solo</option> <option value="Princess Leia">Princess Leia</option> </select> {data ? ( <div> <h2>{data.name}</h2> Height: {data.height} Mass: {data.mass} Hair Color: {data.hair_color} </div> ) : ( Loading... )} </div> ); }; useLayoutEffect 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. Listing 7. useLayoutEffect to measure an element const { useState, useRef, useLayoutEffect } = React; const App = () => { const [width, setWidth] = useState(null); const elementRef = useRef(null); useLayoutEffect(() => { if (elementRef.current) { const { width } = elementRef.current.getBoundingClientRect(); setWidth(width); } }, []); return ( <div> <div ref={elementRef}>Measure Width</div> Width: {width} </div> ); }; useImperativeHandle 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. For this one, the example is worth a thousand words. Listing 8 (live here) shows a parent component, App, with a child component, Expounder. Expounder exposes a customized ref API with useImperativeHandle, such that App can call expound() on the reference, causing the customized behavior (outputting “The way that can be told is not the eternal way” to the UI). Note that the ChildComponent in Listing 8 uses forwardRef to include the DOM ref. Listing 8. useImperativeHandle const { useState, useRef, forwardRef, useImperativeHandle } = React; const ChildComponent = forwardRef((props, ref) => { const [count, setCount] = useState(""); const expound = () => { setCount("The way that can be told is not the eternal way."); }; useImperativeHandle(ref, () => ({ expound })); return ( <div> Count: {count} </div> ); }); const App = () => { const childRef = useRef(null); const handleClick = () => { childRef.current.expound(); }; return ( <div> <ChildComponent ref={childRef} /> <button onClick={handleClick}>Expound</button> </div> ); }; So, useImperativeHandle lets you take the first argument, the original ref, and then decorate it with whatever object is returned by the second argument function. In this case, we use a destructuring assignment to create an anonymous inline object with just an expound method on it: useImperativeHandle(ref, () => ({ expound }));. The result of using the useImperativeHandle hook is that the reference the ParentComponent receives from ChildComponent also has an expound method on it, which it can call to execute the necessary functionality. Conclusion Using a broader palette of hooks available to React is an important aspect of leveraging the full power of the framework. You’ve seen a good sampling of some useful ones here, and React ships with even more. Beyond that, third-party hooks are available for various purposes and for integrating with frameworks. Finally, it’s possible to define your own custom hooks if the need arises. Related content feature 14 great preprocessors for developers who love to code Sometimes it seems like the rules of programming are designed to make coding a chore. Here are 14 ways preprocessors can help make software development fun again. By Peter Wayner Nov 18, 2024 10 mins Development Tools Software Development feature Designing the APIs that accidentally power businesses Well-designed APIs, even those often-neglected internal APIs, make developers more productive and businesses more agile. By Jean Yang Nov 18, 2024 6 mins APIs Software Development news Spin 3.0 supports polyglot development using Wasm components Fermyon’s open source framework for building server-side WebAssembly apps allows developers to compose apps from components created with different languages. By Paul Krill Nov 18, 2024 2 mins Microservices Serverless Computing Development Libraries and Frameworks news Go language evolving for future hardware, AI workloads The Go team is working to adapt Go to large multicore systems, the latest hardware instructions, and the needs of developers of large-scale AI systems. By Paul Krill Nov 15, 2024 3 mins Google Go Generative AI Programming Languages Resources Videos