No history yet

Advanced React Hooks

Beyond the Basics

You've likely used useState and useEffect to manage component state and side effects. But React offers more specialized hooks for trickier situations. These advanced hooks give you precise control over rendering, state logic, and direct interaction with the browser's DOM.

Hooks are the functions which "hook into" React state and lifecycle features from function components.

Let's explore a few of these powerful tools, starting with one that lets you reach outside of React's typical data flow.

useRef: A Persistent Reference

The useRef hook is a bit different from other hooks. Changing a ref does not cause your component to re-render. This makes it perfect for two main jobs: accessing DOM elements directly and keeping track of a mutable value that doesn't affect the visual output.

Lesson image

A classic example of accessing the DOM is to programmatically focus an input field. Instead of trying to manage focus with state, you can attach a ref directly to the input element.

import { useRef } from 'react';

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

The other primary use for useRef is to hold a value that you want to persist across renders, without triggering a re-render when it changes. This is useful for things like storing an interval ID from setInterval so you can clear it later.

import { useState, useEffect, useRef } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  const intervalRef = useRef();

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setSeconds(prevSeconds => prevSeconds + 1);
    }, 1000);

    // The cleanup function runs when the component unmounts
    return () => clearInterval(intervalRef.current);
  }, []); // Empty dependency array means this effect runs only once

  return <h1>{seconds}</h1>;
}

Use useRef for values that you want to change behind the scenes. If a change needs to be reflected in the UI, stick with useState.

useReducer for Complex State

When you find your state logic becoming complex, with multiple useState calls that often change together, it might be time for useReducer. It's an alternative to useState that is often preferred for managing state with multiple sub-values or when the next state depends on the previous one in a complex way.

useReducer accepts a reducer function and an initial state. It returns the current state and a dispatch function.

Here’s a simple counter example. Instead of just setting a new number, we dispatch actions that describe what happened. The reducer function then handles the logic for how the state should change in response.

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return initialState;
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
      <button onClick={() => dispatch({ type: 'increment' })}> + </button>
      <button onClick={() => dispatch({ type: 'decrement' })}> - </button>
    </>
  );
}

By centralizing the update logic in the reducer, you make your component cleaner and the state transitions more predictable. This pattern scales beautifully for more complex applications.

Context and Effects

Two other hooks, useContext and useLayoutEffect, solve very different but equally important problems in React development.

React context, a core React API provides an easier interface for developers to share data or pass props down multiple levels deep in our React applications.

useContext allows you to access a shared value, or "context," without passing props down through every level of your component tree. This is incredibly useful for global data like a user's theme preference, authentication status, or language setting.

First, you create a context. Then you wrap a parent component in a Provider that holds the value. Any child component can then subscribe to that context and read its value using the useContext hook.

// 1. Create the context
const ThemeContext = React.createContext('light');

// 2. Provide the context value at a high level
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// 3. A middle component that doesn't need the prop
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

// 4. A child that consumes the context
function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>I am a {theme} button</button>;
}

Finally, there's useLayoutEffect. Its signature is identical to useEffect, but it fires synchronously after all DOM mutations. This is important for tasks that need to read from the DOM and then synchronously re-render.

For example, if you need to measure the size of a DOM element to position a tooltip, using useLayoutEffect ensures that the tooltip appears in the correct position without a flicker. Because it's synchronous, it can block the browser from painting, so you should use it sparingly and prefer useEffect when possible.

function Tooltip() {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useLayoutEffect(() => {
    if (ref.current) {
      // Measure the DOM element right after it's rendered
      const height = ref.current.offsetHeight;
      setTooltipHeight(height);
    }
  }, []); // Runs once after initial render

  // ... render tooltip using the measured height
}

These advanced hooks are powerful additions to your React toolbox. They help you write cleaner, more efficient, and more maintainable code by providing elegant solutions to common and complex problems.

Quiz Questions 1/5

When you update the current property of a ref object created with useRef, what is the immediate effect on the component?

Quiz Questions 2/5

In which scenario is useReducer generally preferred over multiple useState hooks?