Skip to the content.

React Hooks Cheatsheet

React Hooks Cheatsheet

A quick-reference guide for essential and advanced React Hooks (v18+), covering state, side effects, context, performance optimizations, DOM references, state reducers, and custom hooks.

Rules of Hooks

  1. Only Call Hooks at the Top Level: Do not call Hooks inside loops, conditions, or nested functions. Ensure they are always called in the exact same order on every render.
  2. Only Call Hooks from React Functions: Call Hooks only from React Functional Components or Custom Hooks, never from regular JavaScript functions.

State Hook: useState

useState allows functional components to store and manage local reactive state.

import { useState } from 'react';

function Counter() {
  // Declaration: const [state, setter] = useState(initialValue);
  const [count, setCount] = useState(0);

  // 1. Basic Update
  const increment = () => setCount(count + 1);

  // 2. Functional Update (Always preferred when state depends on the previous state)
  const decrement = () => setCount(prevCount => prevCount - 1);

  // 3. Lazy Initial State (Used for expensive computations - runs only on initial render)
  const [data, setData] = useState(() => {
    return loadExpensiveDataFromStorage();
  });
}

Side Effect Hook: useEffect

useEffect handles asynchronous operations or DOM operations (fetching, subscriptions, manual DOM changes).

import { useEffect, useState } from 'react';

function DataFetcher({ userId }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    let isMounted = true;
    const controller = new AbortController();

    // Effect logic (commonly API fetching)
    async function fetchData() {
      const res = await fetch(`https://api.com/users/${userId}`, { signal: controller.signal });
      const json = await res.json();
      if (isMounted) setData(json);
    }
    fetchData();

    // Cleanup Function (Runs when component unmounts or dependencies change)
    return () => {
      isMounted = false;
      controller.abort();       // Abort active network request to prevent memory leaks
    };
  }, [userId]);                 // Dependency Array: Effect re-runs ONLY when 'userId' changes

  // Dependency Scenarios:
  // useEffect(() => {})         // Runs on EVERY render
  // useEffect(() => {}, [])     // Runs ONCE on initial mount (equivalent to componentDidMount)
  // useEffect(() => {}, [x])    // Runs ONCE on mount, and whenever 'x' changes
}

Context Hook: useContext

useContext consumes values from a React Context directly, avoiding the need for deep prop-drilling.

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

// 1. Create Context
const ThemeContext = createContext(null);

// 2. Provider Component
export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('dark');
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 3. Consume Context (Inside child components)
function ThemeButton() {
  const { theme, setTheme } = useContext(ThemeContext);
  return (
    <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
      Current Theme: {theme}
    </button>
  );
}

Reference Hook: useRef

useRef stores mutable values that do not trigger component re-renders when updated, or acts as a direct reference to access physical DOM nodes.

import { useRef, useEffect } from 'react';

function FocusInput() {
  // Scenario A: Accessing DOM elements
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();   // Focus input element physically on mount
  }, []);

  // Scenario B: Storing mutable values (e.g. interval timers) without triggering re-renders
  const timerRef = useRef(null);
  
  const startTimer = () => {
    timerRef.current = setInterval(() => console.log("Tick"), 1000);
  };
  const stopTimer = () => {
    clearInterval(timerRef.current); // Timer updated without causing a re-render
  };
}

Memoization Hooks: useMemo & useCallback

These hooks cache (memoize) calculations or function references to avoid expensive re-computations or unnecessary re-renders of child components.

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

function Search({ items }) {
  const [query, setQuery] = useState('');

  // 1. useMemo: Caches the computed VALUE of an expensive function
  const filteredItems = useMemo(() => {
    console.log("Expensive filter running...");
    return items.filter(item => item.name.toLowerCase().includes(query.toLowerCase()));
  }, [items, query]);           // Only re-calculates when 'items' or 'query' changes

  // 2. useCallback: Caches the FUNCTION REFERENCE itself
  // Prevents child elements from triggering re-renders due to re-created function references on parent re-renders
  const handleSelect = useCallback((itemId) => {
    console.log("Selected item:", itemId);
  }, []);                       // Empty dependencies: reference remains identical across all re-renders
}

State Reducer Hook: useReducer

useReducer is an alternative to useState preferred for complex state objects, nested state transitions, or when the next state depends heavily on the previous one.

import { useReducer } from 'react';

const initialState = { count: 0, loading: false };

// Reducer Function: purely functional state transitions
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    case 'decrement':
      return { ...state, count: state.count - 1 };
    case 'setLoading':
      return { ...state, loading: action.payload };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

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

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

Custom Hooks

Custom Hooks are custom JavaScript functions whose names start with use. They let you extract and reuse stateful logic between components.

import { useState, useEffect } from 'react';

// Custom Hook to track browser window dimensions
export function useWindowSize() {
  const [size, setWindowSize] = useState({
    width: typeof window !== 'undefined' ? window.innerWidth : 1200,
    height: typeof window !== 'undefined' ? window.innerHeight : 800,
  });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({ width: window.innerWidth, height: window.innerHeight });
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize); // Cleanup
  }, []);

  return size;
}

// Consumer component
function ResponsiveComponent() {
  const { width } = useWindowSize(); // Consuming stateful window size logic instantly
  return <p>Window Width is {width}px</p>;
}