React logoReactv19INTERMEDIATE

React Hooks

React Hooks cheat sheet with useState, useEffect, useRef, useContext, and custom hook patterns with practical code examples.

8 min read
reacthooksstateeffectsrefmemo

Sign in to mark items as known and track your progress.

Sign in

State & Updates

Managing component state and triggering re-renders

useState

Local component state

javascript
// Basic state
const [count, setCount] = useState(0)
const [user, setUser] = useState({ name: 'John' })

// Update patterns
setCount(count + 1)                    // Direct update
setCount(prev => prev + 1)             // Based on previous
setUser({ ...user, name: 'Jane' })     // Object update
💡 State updates are batched automatically
⚡ Use function update for values based on previous state
⚠️ State updates are async - don't read immediately after setting

useReducer

Complex state logic with actions

javascript
// Define reducer
const 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()
  }
}

// Use in component
const [state, dispatch] = useReducer(reducer, { count: 0 })
dispatch({ type: 'increment' })
✅ Better than useState for complex logic
💡 Actions make state changes predictable
⚡ Great for forms with many fields

Side Effects

Handle side effects and external interactions

useEffect

Side effects and lifecycle

javascript
// After every render
useEffect(() => {
  console.log('Rendered')
})

// Once on mount
useEffect(() => {
  fetchData()
}, [])

// When dependencies change
useEffect(() => {
  const timer = setTimeout(() => {}, 1000)
  return () => clearTimeout(timer)      // Cleanup
}, [count, id])
⚠️ Always include dependencies or ESLint will warn
💡 Return cleanup function for subscriptions
✅ Empty deps [] = componentDidMount

useLayoutEffect

Synchronous DOM updates

javascript
// Runs synchronously after DOM mutations
useLayoutEffect(() => {
  // Measure DOM
  const height = ref.current.scrollHeight
  
  // Adjust DOM before browser paint
  ref.current.style.height = `${height}px`
}, [])
⚠️ Blocks visual updates - use sparingly
💡 Same API as useEffect but synchronous

Context & Refs

Share data and access DOM elements

useContext

Consume context values

javascript
// Create context
const ThemeContext = createContext()

// Provider component
<ThemeContext.Provider value={{ dark: true }}>
  <App />
</ThemeContext.Provider>

// React 19: render <Context> directly as the provider
<ThemeContext value={{ dark: true }}>
  <App />
</ThemeContext>

// Consume in any child
const theme = useContext(ThemeContext)

// React 19: use() can read context conditionally (inside if/loops)
const theme2 = use(ThemeContext)

// Custom hook pattern
function useTheme() {
  const context = useContext(ThemeContext)
  if (!context) throw new Error('No ThemeProvider')
  return context
}
💡 Component re-renders when context value changes
⚡ React 19: render <Context value={}> directly (no .Provider); use(Context) reads conditionally
✅ Better than prop drilling for deep trees

useRef

Mutable refs and DOM access

javascript
// DOM reference
const inputRef = useRef(null)
<input ref={inputRef} />
inputRef.current.focus()

// Mutable value (doesn't trigger re-render)
const countRef = useRef(0)
countRef.current++                     // No re-render

// Keep value between renders
const previousValue = useRef(undefined)
useEffect(() => {
  previousValue.current = value
})
⚠️ .current changes don't trigger re-renders
💡 Perfect for storing previous values
✅ Use for DOM elements, timers, subscriptions

Performance

Optimize re-renders and expensive operations

useMemo

Memoize expensive calculations

javascript
// Memoize expensive calculation
const expensiveValue = useMemo(() => {
  return items.reduce((sum, item) => sum + item.value, 0)
}, [items])                             // Recalculate when items change

// Memoize object/array to prevent re-renders
const options = useMemo(() => ({
  color: 'blue',
  size: 'large'
}), [])                                 // Create once
💡 Only use for expensive operations
⚡ React 19's compiler auto-memoizes - often unnecessary now
⚠️ Don't overuse - adds complexity
✅ Great for: sorting, filtering, calculations

useCallback

Memoize functions

javascript
// Memoize function to prevent child re-renders
const handleClick = useCallback(() => {
  doSomething(id)
}, [id])                                // Recreate when id changes

// Without dependencies (never changes)
const handleSubmit = useCallback(() => {
  dispatch({ type: 'submit' })
}, [])                                  // dispatch is stable
💡 Prevents unnecessary child re-renders
⚡ React 19's compiler auto-memoizes - often unnecessary now
✅ Use when passing callbacks to memoized components
⚠️ Don't use for every function

Additional Hooks

Other useful built-in hooks

useImperativeHandle

Customize ref values

javascript
// Child component - ref is a normal prop in React 19 (no forwardRef)
function Child({ ref }) {
  const inputRef = useRef(null)

  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current.focus(),
    clear: () => { inputRef.current.value = '' },
    getValue: () => inputRef.current.value
  }))

  return <input ref={inputRef} />
}

// Parent component
const childRef = useRef(null)
childRef.current.focus()
💡 Expose imperative API to parent
⚡ React 19: ref is a normal prop - no forwardRef needed
⚠️ Use sparingly - breaks React patterns

useId

Generate unique IDs

javascript
// Generate unique ID for accessibility
const id = useId()

return (
  <>
    <label htmlFor={id}>Name:</label>
    <input id={id} />
  </>
)

// Multiple IDs
const id = useId()
const emailId = `${id}-email`
const passwordId = `${id}-password`
✅ SSR-safe unique IDs
💡 Perfect for form labels and ARIA attributes

React 18+ Hooks

New concurrent features

javascript
// useTransition - mark updates as non-urgent
const [isPending, startTransition] = useTransition()

startTransition(() => {
  setSearchQuery(input)                // Non-urgent update
})

// useDeferredValue - defer updating a value
const deferredQuery = useDeferredValue(searchQuery)

// useDebugValue - custom hook debugging
function useCustomHook(value) {
  useDebugValue(value ? 'Active' : 'Inactive')
  return value
}
💡 useTransition for responsive UI during heavy updates
⚡ React 19: startTransition can wrap async functions (Actions)
✅ useDeferredValue for expensive child components
🔍 useDebugValue shows in React DevTools

React 19 Hooks

use, useActionState, useFormStatus & useOptimistic

New hooks introduced in React 19 for promises, forms, and optimistic UI.

tsx
// === use() - read a promise/context inside a component ===
import { use, Suspense } from 'react'

function UserName({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise)        // Suspends until resolved
  return <p>Hello, {user.name}</p>
}

// Wrap with Suspense
<Suspense fallback={<p>Loading...</p>}>
  <UserName userPromise={fetchUser()} />
</Suspense>

// === useActionState - form/action state + pending flag ===
import { useActionState } from 'react'

async function submitAction(prev: State, formData: FormData) {
  const name = formData.get('name')
  // ... server call ...
  return { ok: true, name }
}

function MyForm() {
  const [state, formAction, isPending] = useActionState(submitAction, { ok: false })
  return (
    <form action={formAction}>
      <input name="name" />
      <button disabled={isPending}>{isPending ? 'Saving...' : 'Save'}</button>
    </form>
  )
}

// === useFormStatus - pending state of the enclosing <form> ===
import { useFormStatus } from 'react-dom'

function SubmitButton() {
  const { pending } = useFormStatus()    // Reads parent <form>
  return <button disabled={pending}>{pending ? 'Sending...' : 'Submit'}</button>
}

// === useOptimistic - instant UI while async work runs ===
import { useOptimistic } from 'react'

function Likes({ count, like }: { count: number; like: () => Promise<void> }) {
  const [optimisticCount, addOptimistic] = useOptimistic(
    count,
    (current, _: void) => current + 1,
  )
  return (
    <button onClick={async () => { addOptimistic(); await like() }}>
      👍 {optimisticCount}
    </button>
  )
}
💡 use() reads a promise (or context) and suspends - works inside Suspense
⚡ useActionState replaces useFormState - wraps a server/client action with state + pending
📌 useFormStatus reads the parent <form>'s pending state - only works in children
🔥 useOptimistic shows an instant UI update while an async action is in flight

Custom Hook Patterns

Common custom hooks ready to use

Common Utility Hooks

Frequently used custom hooks

javascript
// Previous value
function usePrevious(value) {
  const ref = useRef(undefined)
  useEffect(() => { ref.current = value })
  return ref.current
}

// Local storage
function useLocalStorage(key, initial) {
  const [value, setValue] = useState(() => {
    const saved = localStorage.getItem(key)
    return saved ? JSON.parse(saved) : initial
  })
  
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value))
  }, [key, value])
  
  return [value, setValue]
}

// Toggle
function useToggle(initial = false) {
  const [on, setOn] = useState(initial)
  const toggle = () => setOn(!on)
  return [on, toggle]
}
💡 Extract common logic into custom hooks
✅ Custom hooks can use other hooks
⚡ Share stateful logic, not state itself

Rules & Best Practices

Essential rules and patterns

Hook Rules

Must follow these rules

javascript
// ✅ DO: Call at top level
function Component() {
  const [state, setState] = useState()
  useEffect(() => {}, [])
  return <div />
}

// ❌ DON'T: Call conditionally
if (condition) {
  useState()                            // ❌ Breaking rules
}

// ❌ DON'T: Call in loops
for (let i = 0; i < 5; i++) {
  useState()                            // ❌ Breaking rules
}

// ✅ DO: Custom hooks start with 'use'
function useCustomHook() {
  const [state, setState] = useState()
  return [state, setState]
}
⚠️ Only call hooks at the top level
⚠️ Only call hooks from React functions
💡 ESLint plugin enforces these rules