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 What is Rust? Safe, fast, and easy software development Unlike most programming languages, Rust doesn't make you choose between speed, safety, and ease of use. Find out how Rust delivers better code with fewer compromises, and a few downsides to consider before learning Rust. By Serdar Yegulalp Nov 20, 2024 11 mins Rust Programming Languages Software Development how-to Kotlin for Java developers: Classes and coroutines Kotlin was designed to bring more flexibility and flow to programming in the JVM. Here's an in-depth look at how Kotlin makes working with classes and objects easier and introduces coroutines to modernize concurrency. By Matthew Tyson Nov 20, 2024 9 mins Java Kotlin Programming Languages analysis Azure AI Foundry tools for changes in AI applications Microsoft’s launch of Azure AI Foundry at Ignite 2024 signals a welcome shift from chatbots to agents and to using AI for business process automation. By Simon Bisson Nov 20, 2024 7 mins Microsoft Azure Generative AI Development Tools news Microsoft unveils imaging APIs for Windows Copilot Runtime Generative AI-backed APIs will allow developers to build image super resolution, image segmentation, object erase, and OCR capabilities into Windows applications. By Paul Krill Nov 19, 2024 2 mins Generative AI APIs Development Libraries and Frameworks Resources Videos