React 19 Hooks Cheat Sheet: All Hooks and When to Use Them
AI generated
</>
{ }
React · Hooks · React 19 · Cheat Sheet
React 19 Hooks Cheat Sheet
all hooks and exactly when to use them

React 19 brings new hooks and refines existing ones, but the biggest problem in practice is not a lack of knowledge, it is the wrong choice: useEffect where useRef would suffice, useMemo where no memoizing is needed at all. This cheat sheet gives clear decision rules for when which React hook is the right one.

18 min read useState · useEffect · useRef · useMemo · useCallback · useTransition · useActionState React 19 · JavaScript · TypeScript

1. Why React hooks changed the component model

Before React Hooks were introduced in version 16.8, every piece of state and every side effect had to be handled through class components using this.setState, lifecycle methods like componentDidMount, and shouldComponentUpdate. The result was nested higher-order components and render props that scattered the same logic across multiple components and were barely testable. React Hooks let you encapsulate stateful logic directly inside function components and reuse it between components via custom hooks, without changing the component hierarchy.

React 19 builds on this foundation and brings new React Hooks such as useActionState, useFormStatus, and improved concurrency features specifically geared toward server components and server actions. Understanding all the React Hooks and their use cases matters more than ever: the wrong choice, such as useMemo for a simple calculation or useEffect for a synchronous transformation, hurts readability and performance at the same time. This cheat sheet gives clear decision rules for every single hook.

2. useState: managing local state simply and safely

useState is the most fundamental React Hook and the first thing to reach for when a component needs to react to user input or internal events. It returns a value/setter pair: const [value, setValue] = useState(initialValue). Every call to setValue triggers a re-render of the component, at which point React delivers the new state value. The initializer function, useState(() => computeExpensiveValue()), is only called on the first render and avoids expensive computations on every re-render. The difference between setValue(newValue) and the functional update setValue(prev => prev + 1) is crucial: the latter reads the current state at execution time, not the state at the time the closure was created.

When useState is the wrong React Hook: if a value changes but should not trigger a re-render, useRef is the right choice. If state must be shared by many components at different levels, consider useContext or an external state manager. If the state logic is complex and several sub-values depend on each other, useReducer is often more readable than several useState calls. As a rule of thumb: use useState for simple, independent values that are tied directly to the rendering of a single component.


import { useState, useReducer } from 'react';

// Simple counter, useState is the right choice here
function Counter() {
  const [count, setCount] = useState(0);

  // Functional update: reads current state, not stale closure value
  const increment = () => setCount(prev => prev + 1);
  const decrement = () => setCount(prev => prev - 1);

  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  );
}

// Complex form state, useReducer is cleaner than multiple useState calls
type FormState = { name: string; email: string; loading: boolean; error: string | null };
type FormAction = { type: 'SET_FIELD'; field: keyof FormState; value: string }
               | { type: 'SET_LOADING'; value: boolean }
               | { type: 'SET_ERROR'; value: string | null };

function formReducer(state: FormState, action: FormAction): FormState {
  switch (action.type) {
    case 'SET_FIELD': return { ...state, [action.field]: action.value };
    case 'SET_LOADING': return { ...state, loading: action.value };
    case 'SET_ERROR': return { ...state, error: action.value };
    default: return state;
  }
}

function ContactForm() {
  const [state, dispatch] = useReducer(formReducer, {
    name: '', email: '', loading: false, error: null,
  });

  return (
    <input value={state.name} onChange={e =>
      dispatch({ type: 'SET_FIELD', field: 'name', value: e.target.value })
    } />
  );
}

3. useEffect: coordinating side effects correctly

useEffect is the most frequently misused React Hook. It is meant for side effects, that is, actions that happen outside the React render cycle: data fetching, manual DOM manipulation, registering event listeners, setting timers, subscribing to external systems. The hook runs after every render unless a dependency array limits its execution. An empty array [] means: run only on the initial mount. The cleanup function returned from the effect runs before the next effect call and on unmount, and it is essential for unsubscribing and cancelling in-flight requests.

What does not belong in useEffect: synchronous transformations of props or state that feed directly into the render output; these belong directly in the render body or in useMemo. Event handlers that only react to direct user interactions should not be coordinated through useEffect. In React 19, and with the React Compiler, many of the previous useEffect patterns for data fetching are replaced by server components and use(). The React Hook useEffect remains indispensable for browser APIs, external libraries, and timers.

4. useRef: DOM access and stable values without re-renders

useRef returns a stable object { current: value } that does not change across re-renders. That makes it the right React Hook for two completely different use cases: first, for direct DOM access, when you pass a ref attribute to an element and then call inputRef.current.focus() programmatically. Second, for values a component needs to use internally without a change triggering a re-render, such as timer IDs, previous values, or flags meant to live outside the React state system.

A common pattern with useRef: storing the previous state value. Since useRef does not trigger a re-render, you can write the current value into the ref inside useEffect after every render; on the next render, ref.current then holds the value from the previous render. This React Hook is also the right choice when you want to always read the latest state inside a callback context without having to recreate the callback on every state change: you store the current state in a ref and read it inside the callback.

5. useContext: sharing global state without prop drilling

useContext is the React Hook for accessing a React context. It returns the current context value provided by the nearest provider in the component hierarchy. This solves the prop-drilling problem: instead of passing a value down through five component levels, you provide it once via a provider and access it directly in every child component with useContext. The hook triggers a re-render of all consuming components on every change to the context value.

That is also the biggest weakness of useContext: if the context value is an object that changes on every render of the provider, all consumers re-render unnecessarily. The correct pattern: stabilize the context value with useMemo, or split the context into separate, finely granular parts, one for frequently changing data, one for stable actions. As a decision rule: useContext is suited to a few changes per second, such as theme, authentication, locale, not to high-frequency state like form fields or animation frames.


import { createContext, useContext, useMemo, useState, ReactNode } from 'react';

// Separate context for data and actions, avoids unnecessary re-renders
interface AuthContextType {
  user: { id: string; name: string } | null;
  isAuthenticated: boolean;
}
interface AuthActionsType {
  login: (credentials: { email: string; password: string }) => Promise<void>;
  logout: () => void;
}

const AuthContext = createContext<AuthContextType | null>(null);
const AuthActionsContext = createContext<AuthActionsType | null>(null);

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<{ id: string; name: string } | null>(null);

  // Stabilize the data object to prevent unnecessary re-renders
  const authData = useMemo<AuthContextType>(
    () => ({ user, isAuthenticated: user !== null }),
    [user]
  );

  // Actions never change, wrap in useMemo so consumers don't re-render
  const authActions = useMemo<AuthActionsType>(() => ({
    login: async (credentials) => {
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        body: JSON.stringify(credentials),
      });
      const data = await response.json();
      setUser(data.user);
    },
    logout: () => setUser(null),
  }), []); // empty deps: these functions never need to change

  return (
    <AuthContext.Provider value={authData}>
      <AuthActionsContext.Provider value={authActions}>
        {children}
      </AuthActionsContext.Provider>
    </AuthContext.Provider>
  );
}

// Custom hooks encapsulate the useContext call with error boundary
export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used within AuthProvider');
  return ctx;
}

export function useAuthActions() {
  const ctx = useContext(AuthActionsContext);
  if (!ctx) throw new Error('useAuthActions must be used within AuthProvider');
  return ctx;
}

6. useMemo and useCallback: when memoizing really helps

useMemo and useCallback are the most frequently overused React Hooks. useMemo caches the result of a computation function between renders as long as its dependencies do not change. useCallback caches the function itself. Both come with overhead: building the dependency array, comparing it on every render, and the memory for the cache. That only pays off when the computation or the function creation is more expensive than this overhead, or when referential stability matters for child components.

The decision rule for useMemo: is the computation genuinely expensive, filtering a large list, aggregating data, complex transformations? Then the React Hook is worth it. For a simple addition or string formatting, useMemo is overhead without benefit. The decision rule for useCallback: is the function passed as a prop to a child component optimized with React.memo or useMemo? Or is it a dependency in another useEffect or useMemo? Then it provides referential stability. Otherwise it is superfluous. With the React Compiler in React 19, the compiler takes over automatic memoizing, making manual use of these hooks noticeably less often necessary.

7. useTransition and useDeferredValue: keeping the UI responsive

useTransition is the React Hook for concurrent features in React 18 and 19. It lets you mark state updates as not urgent: startTransition(() => setFilterValue(input)). React can interrupt and prioritize these updates while it processes urgent updates, such as displaying the typed character in an input field, immediately. The result: a search over a large list feels responsive to the user because the input field reacts instantly while the filtered list is delivered asynchronously.

useDeferredValue is the complementary React Hook: instead of marking the update as not urgent, you get a "deferred" version of the value. The component renders with the old value while React is still computing the new one. This is especially useful when you do not set the value yourself but receive it from a parent, for example as a prop. Both hooks only work in concurrent mode and have no effect in synchronous rendering environments. They are the right tool when expensive renders would otherwise block the UI, without making the user wait for the result before they can keep typing.


import { useState, useTransition, useDeferredValue, useMemo } from 'react';

// Large list filtering, useTransition keeps input responsive
function ProductSearch({ products }: { products: Product[] }) {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();

  // Deferred value: React uses old value while computing new filtered list
  const deferredQuery = useDeferredValue(query);

  // Expensive computation runs with the deferred (potentially stale) value
  const filteredProducts = useMemo(
    () => products.filter(p =>
      p.name.toLowerCase().includes(deferredQuery.toLowerCase()) ||
      p.sku.includes(deferredQuery)
    ),
    [products, deferredQuery]
  );

  const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
    // Urgent update: immediately show what the user typed
    setQuery(e.target.value);

    // Non-urgent update: defer the navigation or heavy side-effect
    startTransition(() => {
      // e.g. update URL params or trigger a non-critical state change
      window.history.replaceState(null, '', `?q=${e.target.value}`);
    });
  };

  return (
    <div>
      <input value={query} onChange={handleSearch} placeholder="Search..." />
      {isPending && <span>Loading...</span>}
      {/* Visually dim the list while a new result is computing */}
      <ul style={{ opacity: deferredQuery !== query ? 0.5 : 1 }}>
        {filteredProducts.map(p => <li key={p.id}>{p.name}</li>)}
      </ul>
    </div>
  );
}

8. useActionState: forms and server actions in React 19

useActionState is the central new React Hook in React 19. It connects an action function to local state and returns three values: the current state, a wrapped action function, and an isPending boolean. The hook is designed for both client and server actions. With server actions, the action function runs on the server, the state is transferred back to the client once it completes, and React synchronizes the component tree automatically. This completely replaces the previous pattern of a manual useState for loading state, a useState for error state, and an onSubmit handler.

The interplay with useFormStatus is important: useFormStatus can be used in a child component of the form and returns the pending state of the parent form's submission. This lets you disable a submit button while a server action is running without passing state down through props. The React Hook useOptimistic complements this pattern: it immediately shows an optimistic value before the server action has completed, and restores the previous value if the action fails. Together, these three React Hooks form the complete pattern for progressive forms in React 19.

9. All React 19 hooks in direct comparison

Choosing the right React Hook is not a matter of style. Wrong decisions lead to unnecessary re-renders, poor performance, data flows that are hard to follow, and bugs caused by stale closure values.

Hook Use case Triggers re-render? Typical mistake
useState Local UI state of a component Yes Stale closure instead of a functional update
useReducer Complex state with action logic Yes Overkill for simple values
useEffect Side effects after render No Missing cleanup function, wrong deps
useRef DOM access, stable values without render No Reading it in the render body (timing issue)
useMemo Caching an expensive computation No Overuse for simple computations
useCallback Keeping a function referentially stable No No effect without a React.memo child
useTransition Marking updates as low priority Yes, deferred Wrapping urgent UI updates
useActionState Forms with server actions (React 19) Yes Only available in React 19

The overview shows: most bugs are not caused by an unknown React Hook, but by confusing similar hooks with each other. Using useRef instead of useState when no re-render is wanted, using useCallback only when the function actually is a dependency of another memoizing hook, using useTransition instead of no hook at all when a filter operation blocks the UI: these are the three most common corrections that code reviews surface in React projects.

Mironsoft

React development, performance optimization, and code reviews

React hooks problems in an existing project?

We analyze React codebases for misused hooks, unnecessary memoizing, stale closure bugs, and performance problems, with concrete recommendations and refactoring plans.

Hook audit

Analysis for misused React hooks, stale closures, and unnecessary memoizing

React 19 migration

Upgrade to React 19, introducing useActionState and server components

Performance review

Re-render analysis, profiler evaluation, and targeted optimizations

10. Summary

This React 19 Hooks Cheat Sheet shows: every React Hook has a clearly defined scope. useState for local UI state that should trigger re-renders. useRef for stable values and DOM access without re-renders. useEffect for side effects after the render, always with cleanup. useContext for rarely changing global values. useMemo and useCallback only where the memoizing cost outweighs the benefit. useTransition for concurrency and responsive UIs during expensive updates.

React 19 extends this set with useActionState for server actions and progressive forms, useFormStatus for submit state in child components, and useOptimistic for optimistic UI updates. With the React Compiler, React also takes over automatic memoizing for most cases, making useMemo and useCallback less often necessary, though understanding their semantics remains essential for debugging and working with older codebases. Anyone who knows all the React Hooks and applies them deliberately writes components that are performant, predictable, and easy to test.

React 19 Hooks Cheat Sheet, the essentials at a glance

State and effects

useState for local state with re-render. useReducer for complex state logic. useEffect for side effects, always with a cleanup function.

References and context

useRef for DOM and stable values without re-render. useContext for global, rarely changing data, stabilize context with useMemo.

Memoizing

useMemo only for expensive computations. useCallback only when the function is a dependency of another hook or a prop for an optimized child. Often superfluous with the React Compiler.

React 19 new hooks

useActionState for server actions and forms. useTransition for responsive UIs during expensive updates. useOptimistic for optimistic states.

11. FAQ: React 19 Hooks Cheat Sheet

1useState instead of useRef, when?
useState when the change should trigger a re-render and an updated output. useRef for internal values with no impact on rendering.
2Why is useEffect so often misused?
Often used for synchronous transformations that belong in the render body. Missing cleanup leads to memory leaks. Only meant for asynchronous side effects and browser APIs.
3When is useMemo genuinely worth it?
For expensive computations such as filtering large lists. For simple values, the overhead is larger than the benefit. React Compiler takes over automatic memoizing in React 19.
4What is useActionState?
A new React 19 hook for forms with server actions. Connects an action function with state, returns the current state, a wrapped action, and an isPending flag.
5useTransition vs. useDeferredValue?
useTransition when you set the state yourself and mark it as not urgent. useDeferredValue for a value received from outside that should be processed with a delay.
6When to use useCallback?
Only when the function is a prop for a React.memo child or a dependency in useEffect/useMemo. Otherwise useCallback only creates overhead with no benefit.
7Avoiding stale closure bugs?
Functional update pattern: setValue(prev => prev + 1). For callbacks, mirror the current state in a useRef and read from the ref.
8useContext for high-frequency state?
Not recommended. Every context value change re-renders all consumers. For high-frequency state, use local useState or an external store.
9React Compiler and useMemo/useCallback?
React Compiler inserts automatic memoizing. Manual useMemo/useCallback calls become superfluous in many cases. Understanding remains important for debugging and legacy code.
10Testing custom hooks correctly?
With renderHook from Testing Library. act() wrapping for state updates. Pass a provider as the wrapper option when the hook uses useContext.