How many hooks are in React 19?

12 min read
·

A Complete List of Hooks Available in React 19

There are a total of 19 hooks, as listed below:

  • State Management Hooks
    • useState
    • useReducer
  • Context Hook
    • useContext
  • Reference Hooks
    • useRef
    • useImperativeHandle
  • Side Effect Hooks
    • useEffect
    • useLayoutEffect
    • useInsertionEffect
  • Performance Optimization Hooks
    • useMemo
    • useCallback
  • Scheduling Hooks
    • useTransition
    • useDeferredValue
  • Other Hooks
    • useId
    • useSyncExternalStore
    • useDebugValue
  • New Hooks in React 19
    • use
    • useActionState
    • useFormStatus
    • useOptimistic

useState

useState allow function components to have and manage their own state, enabling components to response to changes and re-render.

Here's an example of how to use it:

import React, { useState } from 'react';

export default function SimpleCounter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

useReducer

useReducer is used for managing more complex state logic. It is similar to useState but is better suited for handling multiple related state values or when the next state depends on the previous state.

Here's an example of how to use it:

import React, { useReducer } from 'react';

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

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

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

useContext

useContext is used to share data within a component tree without having to pass props through each component layer. It allows components to directly access context defined in a parent component.

Here's an example of how to use it:

import React, { createContext, useContext, useState } from 'react';

const ThemeContext = createContext('light');

function DisplayTheme() {
  const theme = useContext(ThemeContext);
  return <div>Current theme: {theme}</div>;
}

export default function App() {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={theme}>
      <DisplayTheme />
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
    </ThemeContext.Provider>
  );
}

useRef

useRef is used to create a mutable reference that persists throughout the component's lifecycle without triggering re-renders. It's commonly used to store references to DOM elements or any mutable value that doesn't require re-rendering.

Here's an example of how to use it:

import React, { useRef } from 'react';

export default function FocusInput() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

useImperativeHandle

useImperativeHandle allows you to customize the instance value that is exposed to the parent component when using a ref. This Hook is typically used with forwardRef to expose custom methods or properties to the parent component.

Here's an example in TypeScript for clarity:

import React, { useRef, useImperativeHandle, forwardRef, useState } from 'react';

type CounterRef = {
  increment: () => void;
  getCount: () => number;
};

const CustomCounter = forwardRef<CounterRef>((props, ref) => {
  const [count, setCount] = useState(0);

  // The ref holds the object returned by the second argument
  useImperativeHandle(ref, () => ({
    increment: () => setCount(count + 1),
    getCount: () => count
  }));

  return <div>Count: {count}</div>;
});

export default function App() {
  const counterRef = useRef<CounterRef>(null);

  const handleClick = () => {
    if (counterRef.current) {
      counterRef.current.increment();
      console.log('Current count:', counterRef.current.getCount());
    }
  };

  return (
    <div>
      <CustomCounter ref={counterRef} />
      <button onClick={handleClick}>Increment and Log Count</button>
    </div>
  );
}

useEffect

useEffect is used to perform side effects in function components, such as data fetching, subscriptions, or manually modifying the DOM. It runs after the component renders, and its execution timing can be controlled through a dependency array.

Here's an example of how to use it:

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

export default function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setCount((prevCount) => prevCount + 1);
    }, 1000);

    return () => {
      clearInterval(timer);
    };
  }, []);

  return <div>Timer: {count} seconds</div>;
}

useLayoutEffect

useLayoutEffect works the same as useEffect, with the difference in when it fires. It runs synchronously after all DOM changes but before the browser has painted. Because this process is synchronous, time-consuming operations in useLayoutEffect can block page rendering. Generally, useEffect should be preferred unless you need to access or modify the DOM before the paint.

Here's an example of how to use it:

import React, { useLayoutEffect } from 'react';

export default function LogDOMInfo() {
  useLayoutEffect(() => {
    const mainElement = document.querySelector('main');
    if (mainElement) {
      console.log('Main element width:', mainElement.clientWidth);
      console.log('Main element height:', mainElement.clientHeight);
    }
  }, []);

  return (
    <main>
      <h1>Hello, useLayoutEffect!</h1>
      <p>Check the console for main element dimensions.</p>
    </main>
  );
}

useInsertionEffect

useInsertionEffect is a React Hook designed specifically for CSS-in-JS libraries. It fires synchronously after DOM changes but before layout effects read the new layout. This Hook is primarily used to inject styles into the DOM before any layout shifts occur, avoiding layout jank.

Here's an example of how to use it:

import React, { useState, useInsertionEffect } from 'react';

// Simulate a simple CSS-in-JS
const injectStyle = (css: string) => {
  const style = document.createElement('style');
  style.textContent = css;
  document.head.appendChild(style);
  return style;
};

export default function DynamicStyle() {
  const [color, setColor] = useState('red');

  useInsertionEffect(() => {
    const style = injectStyle(`
      .dynamic-text {
        color: ${color};
        font-size: 24px;
        font-weight: bold;
      }
    `);

    return () => {
      document.head.removeChild(style);
    };
  }, [color]);

  return (
    <div>
      <p className="dynamic-text">This text has dynamic styling</p>
      <button onClick={() => setColor(color === 'red' ? 'blue' : 'red')}>
        Toggle Color
      </button>
    </div>
  );
}

useMemo

useMemo is used to memoize the result of a calculation. It takes a function and dependency array, and only recalculates the result when dependencies change. This is useful for optimizing values that are computationally expensive.

Here's an example of how to use it:

import React, { useState, useMemo } from 'react';

function expensiveCalculation(num: number): number {
  console.log('Calculating...');
  for (let i = 0; i < 1000000000; i++) {
    num += 1;
  }
  return num;
}

export default function Calculator() {
  const [num, setNum] = useState(1);

  // expensiveResult will only be recalculated when num changes, if num remains the same, it won't be recalculated
  const expensiveResult = useMemo(() => expensiveCalculation(num), [num]);

  return (
    <div>
      <h2>Expensive Calculation Result: {expensiveResult}</h2>
      <button onClick={() => setNum(num + 1)}>Change Calculation Input</button>
    </div>
  );
}

useCallback

useCallback is used to memoize a function definition. It takes a callback and a dependency array and returns a new function only if dependenciees change. This is useful for optimizing functions passed as props to child components.

Here's an example of how to use it:

In the following example, you can see Button Button "Button 2" rendered in console, but not Button "Button 1" rendered. This is because useCallback's dependency array hasn't changed, so it returns the same function instead of recreating it each time.

import React, { useState, useCallback, memo } from 'react';

const ExpensiveButton = memo(
  ({ onClick, label }: { onClick: () => void; label: string }) => {
    console.log(`Button "${label}" rendered`);
    return <button onClick={onClick}>{label}</button>;
  },
);

export default function CallbackExample() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  const incrementCount1 = useCallback(() => {
    setCount1((prevCount) => prevCount + 1);
  }, []);

  const incrementCount2 = () => {
    setCount2((prevCount) => prevCount + 1);
  };

  console.log('Parent component rendered');

  return (
    <div>
      <h2>With useCallback: {count1}</h2>
      <ExpensiveButton onClick={incrementCount1} label="Button 1" />

      <h2>Without useCallback: {count2}</h2>
      <ExpensiveButton onClick={incrementCount2} label="Button 2" />

      <p>Check the console to see which components are re-rendering.</p>
    </div>
  );
}

useTransition

useTransition provides a flag to indicate the loading state, which developers can use to improve the user experience, espencially when handling slower updates that may cause the UI to lag.

Here's an example of how to use it:

import React, { useState, useTransition } from 'react';

function SlowList({ text }: { text: string }) {
  const items = [];
  for (let i = 0; i < 500; i++) {
    items.push(<li key={i}>{text}</li>);
  }
  return <ul>{items}</ul>;
}

export default function TransitionExample() {
  const [text, setText] = useState('');
  const [deferredText, setDeferredText] = useState('');
  const [isPending, startTransition] = useTransition();

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    setText(e.target.value);
    startTransition(() => {
      setDeferredText(e.target.value);
    });
  }

  return (
    <div>
      <input value={text} onChange={handleChange} />
      <p>Typed text: {text}</p>
      {isPending ? (
        <p>Updating list...</p>
      ) : (
        <SlowList text={deferredText} />
      )}
    </div>
  );
}

useDeferredValue

useDeferredValue is used to delay updating a certain value. It takes a value and returns a new copy of that value, which updates more slowly. This is useful for postponing the rendering of more computationally expensive parts.

In the example below, if you type or delete content quickly, the value of deferredText will take a moment to update.

useDeferredValue demo gif

import React, { useState, useDeferredValue, memo } from 'react';

const SlowList = memo(function SlowList({ text }: { text: string }) {
  console.log('Rendering SlowList');
  const items = [];
  for (let i = 0; i < 10000; i++) {
    items.push(<li key={i}>{text}</li>);
  }
  return <ul>{items}</ul>;
});

export default function DeferredValueExample() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    setText(e.target.value);
  }

  return (
    <div>
      <input
        value={text}
        onChange={handleChange}
        placeholder="Type here..."
        style={{ fontSize: '18px', padding: '5px', width: '200px' }}
      />
      <p>Immediate text: {text}</p>
      <p style={{ color: text === deferredText ? 'black' : 'red' }}>
        Deferred text: {deferredText}
      </p>
      <SlowList text={text} />
    </div>
  );
}

useId

useId is used to generate a unique ID.

Here's an example of how to use it:

useId demo img

import React, { useId } from 'react';

function LabeledInput({ label }: { label: string }) {
  const id = useId();
  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} type="text" />
    </div>
  );
}

export default function UniqueIdExample() {
  return (
    <div>
      <h2>Form with Unique IDs</h2>
      <LabeledInput label="First Name" />
      <LabeledInput label="Last Name" />
      <LabeledInput label="Email" />
    </div>
  );
}

useSyncExternalStore

useSyncExternalStore is used to subscribe to an external data source, facilitating integration between React and external state libraries.

Here's an example of how to use it:

import React, { useSyncExternalStore } from 'react';

// Simulate an external store
const createStore = (initialState: number) => {
  let state = initialState;
  const listeners = new Set<() => void>();

  return {
    subscribe: (listener: () => void) => {
      listeners.add(listener);
      return () => listeners.delete(listener);
    },
    getSnapshot: () => state,
    increment: () => {
      state++;
      listeners.forEach(listener => listener());
    }
  };
};

const store = createStore(0);

function Counter() {
  const count = useSyncExternalStore(
    store.subscribe,
    store.getSnapshot
  );

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={store.increment}>Increment</button>
    </div>
  );
}

export default function ExternalStoreExample() {
  return (
    <div>
      <h2>External Store Example</h2>
      <Counter />
      <Counter />
    </div>
  );
}

useDebugValue

useDebugValue is used to add labels for custom Hooks in React DevTools. It allows developers to provide more meaning full debug information for a custom Hook, making it easier to understand the Hook's current state when inspecting components in React DevTools.

Here's an example of how to use it:

useDebugValue demo image

import React, { useState, useEffect, useDebugValue } from 'react';

// Custom Hook : useOnlineStatus
function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  // Using useDebugValue to add debug information
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

function StatusIndicator() {
  const isOnline = useOnlineStatus();

  return (
    <div>
      <h2>Network Status</h2>
      <p>You are {isOnline ? 'online' : 'offline'}</p>
    </div>
  );
}

export default function DebugValueExample() {
  return (
    <div>
      <h1>useDebugValue Example</h1>
      <StatusIndicator />
    </div>
  );
}

use

use is a new React Hook that works with Suppense to wait for resource loading during the redering process.

Here's an example of how to use it:

use demo gif

import React, { use, Suspense } from 'react';

// Simulate asynchronous data loading
const fetchComments = () =>
  new Promise((resolve) =>
    setTimeout(() => resolve(['Comment 1', 'Comment 2', 'Comment 3']), 1000),
  );

function Comments() {
  const comments = use(fetchComments());
  return (
    <ul>
      {comments.map((comment, index) => (
        <li key={index}>{comment}</li>
      ))}
    </ul>
  );
}

export default function UseExample() {
  return (
    <div>
      <h1>Comments</h1>
      <Suspense fallback={<div>Loading comments...</div>}>
        <Comments />
      </Suspense>
    </div>
  );
}

useActionState

useActionState is used to manage the state of form submissions. Traditionally, handling form data requires manually creating multiple state, but with useActionState, you can manage these without manual state handling.

Here's an example of how to use it:

'use client';

import React, { useActionState } from 'react';

// Simulate a server action
async function submitForm(prevState, formData) {
  // Simalate network request
  await new Promise((resolve) => setTimeout(resolve, 1000));

  const email = formData.get('email');
  if (!email.includes('@')) {
    return { error: 'Invalid email address' };
  }

  return { success: true, message: 'Form submitted successfully' };
}

export default function ActionStateExample() {
  const [state, formAction, pending] = useActionState(submitForm, {
    error: null,
    success: false,
    message: '',
  });

  return (
    <div>
      <h1>useActionState Example</h1>
      <form action={formAction}>
        <div>
          <label htmlFor="email">Email:</label>
          <input type="email" id="email" name="email" required />
        </div>
        <button type="submit" disabled={pending}>
          {pending ? 'Submitting...' : 'Submit'}
        </button>
      </form>
      {state.error && <p style={{ color: 'red' }}>{state.error}</p>}
      {state.success && <p style={{ color: 'green' }}>{state.message}</p>}
    </div>
  );
}

useFormStatus

useFormStatus is used to retrieve the status information of the nearest ancestor <form> element. For example, it can be utilized to adjust the style of a SubmitButton during form submission.

Here's an example of how to use it:

import React from 'react';
import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button
      type="submit"
      disabled={pending}
      className={`px-4 py-2 rounded ${pending ? 'bg-gray-400' : 'bg-blue-500 hover:bg-blue-600'} text-white`}
    >
      {pending ? 'Submitting...' : 'Submit'}
    </button>
  );
}

function handleSubmit(formData) {
  // Simulate an asynchronous operation
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Form submitted with:', Object.fromEntries(formData));
      resolve(null);
    }, 2000);
  });
}

export default function FormStatusExample() {
  return (
    <form action={handleSubmit} className="space-y-4">
      <div>
        <label
          htmlFor="name"
          className="block text-sm font-medium text-gray-700"
        >
          Name:
        </label>
        <input
          type="text"
          id="name"
          name="name"
          required
          className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
        />
      </div>
      <div>
        <label
          htmlFor="email"
          className="block text-sm font-medium text-gray-700"
        >
          Email:
        </label>
        <input
          type="email"
          id="email"
          name="email"
          required
          className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
        />
      </div>
      <SubmitButton />
    </form>
  );
}

useOptimistic

useOptimistic is used to implement optimistic updates, allowing you to immediately update the UI while waiting for an asynchronous operation to complete, providing a faster user experience.

Here's an example of how to use it:

useOptimistic demo gif

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

async function deliverMessage(message) {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  return message;
}

function Thread({ messages, sendMessage }) {
  const formRef = useRef();
  async function formAction(formData) {
    addOptimisticMessage(formData.get('message'));
    formRef.current.reset();
    await sendMessage(formData);
  }
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessage) => [
      ...state,
      {
        text: newMessage,
        sending: true,
      },
    ],
  );

  return (
    <>
      {optimisticMessages.map((message, index) => (
        <div key={index}>
          {message.text}
          {!!message.sending && <small> (Sending...)</small>}
        </div>
      ))}
      <form action={formAction} ref={formRef}>
        <input type="text" name="message" placeholder="Hello!" />
        <button type="submit">Send</button>
      </form>
    </>
  );
}

export default function App() {
  const [messages, setMessages] = useState([
    { text: 'Hello there!', sending: false, key: 1 },
  ]);
  async function sendMessage(formData) {
    const sentMessage = await deliverMessage(formData.get('message'));
    setMessages((messages) => [...messages, { text: sentMessage }]);
  }
  return <Thread messages={messages} sendMessage={sendMessage} />;
}