MeshWorld India Logo MeshWorld.
Cheatsheet React JavaScript Frontend Web Dev Developer Tools 10 min read

React Hooks Cheat Sheet: All Hooks with React 19

Scarlett
By Scarlett
| Updated: May 20, 2026
React Hooks Cheat Sheet: All Hooks with React 19
TL;DR
  • React 19 (stable Dec 2024) new hooks: use(), useOptimistic, useFormStatus, useActionState
  • useState for values, useReducer for complex state logic, useRef for DOM and mutable values
  • useEffect for side effects — always return a cleanup function when subscribing
  • useMemo caches computed values, useCallback caches function references — don’t overuse
  • Hooks only work in function components (not class components) and cannot be called conditionally

Quick reference — all hooks at a glance

| Hook | Purpose | Re-renders? | |---|---|---| | useState | Local state value | Yes, on state change | | useReducer | Complex state machine | Yes, on dispatch | | useEffect | Side effects (fetch, subscribe, DOM) | No | | useLayoutEffect | Same as useEffect but fires sync before paint | No | | useRef | Mutable ref (DOM or any value) | No | | useMemo | Cache a computed value | No | | useCallback | Cache a function reference | No | | useContext | Read from a Context | Yes, on context value change | | useId | Generate stable unique IDs | No | | useTransition | Mark state update as non-urgent | No | | useDeferredValue | Defer a value update (like debounce) | No | | useImperativeHandle | Expose methods on a ref to parent | No | | useDebugValue | Label custom hook in DevTools | No | | use() (React 19) | Read a Promise or Context (anywhere) | Suspends | | useOptimistic (React 19) | Show optimistic UI during async action | Yes | | useFormStatus (React 19) | Read parent form’s submission state | Yes | | useActionState (React 19) | Manage state driven by form actions | Yes |


useState

jsx
const [value, setValue] = useState(initialValue)
jsx
// Number
const [count, setCount] = useState(0)

// Object — always spread when updating
const [user, setUser] = useState({ name: 'Alice', age: 30 })
setUser(prev => ({ ...prev, age: 31 }))   // Correct
setUser({ age: 31 })                        // Wrong — loses name

// Array — always return new array
const [items, setItems] = useState([])
setItems(prev => [...prev, newItem])        // Add
setItems(prev => prev.filter(i => i.id !== id))  // Remove

// Lazy initialization (runs only once)
const [data, setData] = useState(() => expensiveComputation())
Stale Closure Trap

Always use the functional form setState(prev => ...) when the new state depends on the old state. Using setState(count + 1) inside a closure captures a stale count if called multiple times in the same render cycle.


useReducer

Use when state transitions are complex or multiple sub-values update together:

jsx
const initialState = { count: 0, step: 1 }

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

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

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

useEffect

jsx
// Runs after every render
useEffect(() => { ... })

// Runs once (on mount)
useEffect(() => { ... }, [])

// Runs when dep1 or dep2 changes
useEffect(() => { ... }, [dep1, dep2])

// With cleanup (unsubscribe, clear timer, abort fetch)
useEffect(() => {
    const controller = new AbortController()
    fetch('/api/data', { signal: controller.signal })
        .then(r => r.json())
        .then(setData)

    return () => controller.abort()   // Cleanup on unmount or re-run
}, [id])

| Pattern | [] deps | Notes | |---|---|---| | Fetch on mount | [] | Add cleanup to abort the request | | Subscribe to event | [] | Return () => window.removeEventListener(...) | | Re-fetch on prop change | [id] | Include all values read from the component | | Run on every render | omit array | Rare — usually a bug |

React StrictMode Runs Effects Twice

In development, StrictMode mounts → unmounts → mounts every component to catch missing cleanups. Your cleanup function must correctly undo the effect or you’ll see double-fire behavior in dev that is invisible in production.


useRef

Two uses: hold a DOM node, or hold any mutable value that does NOT trigger re-renders:

jsx
// 1 — DOM reference
function TextInput() {
    const inputRef = useRef(null)

    return (
        <>
            <input ref={inputRef} />
            <button onClick={() => inputRef.current.focus()}>Focus</button>
        </>
    )
}

// 2 — Mutable value (like an instance variable)
function Timer() {
    const intervalRef = useRef(null)

    const start = () => {
        intervalRef.current = setInterval(() => tick(), 1000)
    }

    const stop = () => {
        clearInterval(intervalRef.current)
    }

    return <><button onClick={start}>Start</button><button onClick={stop}>Stop</button></>
}

useMemo and useCallback

Both are performance tools. Only add them when you have a measured problem.

useMemo — cache a computed value

jsx
// Without useMemo: runs on every render
const sorted = items.sort((a, b) => a.price - b.price)

// With useMemo: only re-runs when items changes
const sorted = useMemo(
    () => [...items].sort((a, b) => a.price - b.price),
    [items]
)

useCallback — cache a function reference

jsx
// Without useCallback: new function reference every render
// This causes child to re-render even if nothing changed
const handleClick = () => doSomething(id)

// With useCallback: same reference until id changes
const handleClick = useCallback(
    () => doSomething(id),
    [id]
)

| When to use | When NOT to use | |---|---| | Passing callback to React.memo child | Callbacks called once, not passed down | | Expensive array/object transformations | Simple value reads | | Dependency of another hook | Every function by default |


useContext

jsx
// 1 — Create a context
const ThemeContext = createContext('light')

// 2 — Provide it high in the tree
function App() {
    const [theme, setTheme] = useState('light')
    return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
            <Layout />
        </ThemeContext.Provider>
    )
}

// 3 — Consume it anywhere below
function Button() {
    const { theme } = useContext(ThemeContext)
    return <button className={theme}>Click</button>
}

useTransition and useDeferredValue

Both make UI feel faster by deferring non-urgent updates:

jsx
// useTransition — mark an update as non-urgent
const [isPending, startTransition] = useTransition()

function handleSearch(query) {
    // Urgent: update the input immediately
    setInputValue(query)

    // Non-urgent: update search results (won't block typing)
    startTransition(() => {
        setSearchResults(filterData(query))
    })
}

// Show a loading indicator while transition is running
{isPending && <Spinner />}
jsx
// useDeferredValue — defer a value (React controls timing)
const deferredQuery = useDeferredValue(searchQuery)

// Pass deferredQuery to expensive component instead of searchQuery
<ExpensiveList query={deferredQuery} />

React 19 hooks

use() — read Promises and Context anywhere

jsx
// Read a promise (must be wrapped in Suspense)
function UserProfile({ userPromise }) {
    const user = use(userPromise)   // Suspends until resolved
    return <h1>{user.name}</h1>
}

<Suspense fallback={<Spinner />}>
    <UserProfile userPromise={fetchUser(id)} />
</Suspense>

// Read context (same as useContext, but can be conditional)
const theme = use(ThemeContext)

useActionState — form state driven by Server Actions

jsx
async function submitForm(prevState, formData) {
    const name = formData.get('name')
    if (!name) return { error: 'Name required' }
    await saveToDatabase(name)
    return { success: true }
}

function Form() {
    const [state, formAction, isPending] = useActionState(submitForm, null)

    return (
        <form action={formAction}>
            <input name="name" />
            <button disabled={isPending}>
                {isPending ? 'Saving...' : 'Save'}
            </button>
            {state?.error && <p>{state.error}</p>}
        </form>
    )
}

useFormStatus — read parent form state

jsx
// Must be used in a component INSIDE the form
function SubmitButton() {
    const { pending, data, method } = useFormStatus()
    return <button disabled={pending}>{pending ? 'Submitting...' : 'Submit'}</button>
}

function Form() {
    return (
        <form action={serverAction}>
            <input name="email" />
            <SubmitButton />   {/* useFormStatus reads this form */}
        </form>
    )
}

useOptimistic — optimistic UI updates

jsx
function MessageList({ messages, sendMessage }) {
    const [optimisticMessages, addOptimistic] = useOptimistic(
        messages,
        (state, newMsg) => [...state, { ...newMsg, sending: true }]
    )

    async function handleSend(formData) {
        const text = formData.get('text')
        addOptimistic({ text })     // Show immediately
        await sendMessage(text)      // Then actually send
    }

    return (
        <>
            {optimisticMessages.map(m => (
                <div key={m.id} style={{ opacity: m.sending ? 0.5 : 1 }}>
                    {m.text}
                </div>
            ))}
            <form action={handleSend}>
                <input name="text" />
                <button>Send</button>
            </form>
        </>
    )
}

Custom hooks

Extract reusable logic into a function named use*:

jsx
// useLocalStorage
function useLocalStorage(key, initialValue) {
    const [value, setValue] = useState(
        () => JSON.parse(localStorage.getItem(key) ?? JSON.stringify(initialValue))
    )

    const setStored = (newValue) => {
        setValue(newValue)
        localStorage.setItem(key, JSON.stringify(newValue))
    }

    return [value, setStored]
}

// useDebounce
function useDebounce(value, delay = 300) {
    const [debounced, setDebounced] = useState(value)

    useEffect(() => {
        const t = setTimeout(() => setDebounced(value), delay)
        return () => clearTimeout(t)
    }, [value, delay])

    return debounced
}

// useFetch
function useFetch(url) {
    const [data, setData] = useState(null)
    const [loading, setLoading] = useState(true)
    const [error, setError] = useState(null)

    useEffect(() => {
        const controller = new AbortController()
        setLoading(true)
        fetch(url, { signal: controller.signal })
            .then(r => r.json())
            .then(setData)
            .catch(e => { if (e.name !== 'AbortError') setError(e) })
            .finally(() => setLoading(false))
        return () => controller.abort()
    }, [url])

    return { data, loading, error }
}

Rules of Hooks

plaintext
✅ Call hooks at the top level of a function component
✅ Call hooks at the top level of a custom hook
✅ Name custom hooks with the use prefix

❌ Call hooks inside if/else, loops, or nested functions
❌ Call hooks in class components
❌ Call hooks in regular JavaScript functions
❌ Call hooks conditionally

Summary

  • useState + useReducer = all state needs. Use reducer when you have 3+ related state values or complex transitions.
  • useEffect cleanup is mandatory for subscriptions, timers, and fetches.
  • useRef does not cause re-renders — use it for DOM access and non-reactive values.
  • useMemo / useCallback are optimization tools, not defaults — measure first.
  • React 19’s use(), useActionState, useFormStatus, useOptimistic make async and form patterns vastly simpler.

FAQ

When should I use useReducer instead of useState? When the next state depends on a complex combination of the previous state, or when you have 3+ related values that always update together. useState is fine for simple, independent values.

Why does my useEffect run twice in development? React’s StrictMode intentionally double-invokes effects to surface missing cleanup functions. It only happens in development mode. Fix it by returning a proper cleanup from the effect.

Can I call a hook from inside a condition? No. The order of hook calls must be the same on every render — React uses call order to associate state with the right hook. Conditionally render a component instead, and put the hook inside that component.

What is the difference between useMemo and useCallback? useMemo returns the result of a function (useMemo(() => compute(), [deps])). useCallback returns the function itself (useCallback(() => doThing(), [deps])). useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

Do hooks work in React Server Components? No. Server Components run on the server and don’t have state or lifecycle. Hooks only work in Client Components (files with "use client" or in classic React without the framework).