React Hooks Cheat Sheet

Complete guide to all React hooks with practical examples, use cases, and best practices

What are React Hooks?

Hooks are functions that let you "hook into" React state and lifecycle features from function components. Introduced in React 16.8, hooks provide a more direct API to the React concepts you already know: props, state, context, refs, and lifecycle.

Hooks let you use state and other React features without writing a class. They make it easier to reuse stateful logic between components and organize component code by related functionality.

Rules of Hooks

  • Only call hooks at the top level - Don't call hooks inside loops, conditions, or nested functions
  • Only call hooks from React functions - Call from function components or custom hooks only
  • Use the ESLint plugin - Install eslint-plugin-react-hooks to enforce these rules

useState

const [state, setState] = useState(initialState)

The useState hook lets you add state to functional components. It returns a pair: the current state value and a function to update it.

Basic Counter Example
import React, { useState } from 'react';

function Counter() {
  // Declare a state variable "count" with initial value 0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
Functional Updates
function Counter() {
  const [count, setCount] = useState(0);

  // Functional update - recommended when new state depends on previous state
  const increment = () => {
    setCount(prevCount => prevCount + 1);
  };

  // Multiple updates in same event will batch correctly
  const incrementByFive = () => {
    setCount(c => c + 1);
    setCount(c => c + 1);
    setCount(c => c + 1);
    setCount(c => c + 1);
    setCount(c => c + 1);
  };

  return <button onClick={increment}>Count: {count}</button>;
}

Common Use Cases

  • Managing form inputs and controlled components
  • Toggling UI elements (modals, dropdowns, menus)
  • Tracking component-specific data (counters, selections)
  • Storing user preferences and settings

useEffect

useEffect(() => { /* effect */ }, [dependencies])

The useEffect hook lets you perform side effects in function components. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

Basic Effect
import { useState, useEffect } from 'react';

function DocumentTitle() {
  const [count, setCount] = useState(0);

  // Runs after every render (no dependency array)
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <button onClick={() => setCount(count + 1)}>
      Click me
    </button>
  );
}
Effect with Cleanup
function ChatRoom({ roomId }) {
  useEffect(() => {
    // Setup: Subscribe to chat room
    const connection = createConnection(roomId);
    connection.connect();

    // Cleanup function: Unsubscribe when component unmounts or roomId changes
    return () => {
      connection.disconnect();
    };
  }, [roomId]); // Only re-run if roomId changes

  return <div>Connected to {roomId}</div>;
}
Dependency Array Examples
// Runs after EVERY render
useEffect(() => {
  console.log('Runs on every render');
});

// Runs only ONCE after initial render (like componentDidMount)
useEffect(() => {
  console.log('Runs only once');
}, []);

// Runs when count or name changes
useEffect(() => {
  console.log('Runs when count or name changes');
}, [count, name]);

Common Use Cases

  • Fetching data from APIs
  • Setting up subscriptions or event listeners
  • Manually changing the DOM
  • Logging and analytics
  • Setting timers and intervals

Best Practices

  • Always specify dependencies array to avoid infinite loops
  • Return cleanup function for subscriptions and timers
  • Use multiple useEffect hooks to separate concerns
  • Don't forget async functions need a wrapper inside useEffect

useContext

const value = useContext(MyContext)

The useContext hook accepts a context object and returns the current context value. It's a cleaner way to consume context compared to Context.Consumer.

Theme Context Example
import { createContext, useContext, useState } from 'react';

// Create context
const ThemeContext = createContext();

// Provider component
function App() {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// Consumer component using useContext
function ThemedButton() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <button
      style={{ background: theme === 'dark' ? '#333' : '#fff' }}
      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
    >
      Toggle Theme
    </button>
  );
}

Common Use Cases

  • Theme switching (dark mode / light mode)
  • User authentication state
  • Language/localization settings
  • Global app configuration

useReducer

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

The useReducer hook is an alternative to useState for managing complex state logic. It's preferable when you have multiple sub-values or when the next state depends on the previous one.

Counter with Reducer
import { useReducer } from 'react';

// Reducer function
function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    default:
      throw new Error('Unknown action');
  }
}

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

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

When to Use useReducer

  • Complex state logic with multiple sub-values
  • Next state depends on previous state
  • Need to optimize performance for deep updates
  • Prefer Redux-like state management patterns

useRef

const refContainer = useRef(initialValue)

The useRef hook returns a mutable ref object whose .current property is initialized to the passed argument. The returned object will persist for the full lifetime of the component.

Accessing DOM Elements
import { useRef, useEffect } from 'react';

function TextInputWithFocusButton() {
  const inputRef = useRef(null);

  const focusInput = () => {
    // Access DOM element directly
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}
Storing Mutable Values
function Timer() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef();

  useEffect(() => {
    // Store interval ID in ref
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);

    return () => clearInterval(intervalRef.current);
  }, []);

  const stopTimer = () => {
    clearInterval(intervalRef.current);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}

Common Use Cases

  • Accessing DOM elements directly
  • Storing interval or timeout IDs
  • Keeping previous values across renders
  • Storing any mutable value that doesn't trigger re-renders

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])

The useMemo hook memoizes expensive computations and only recomputes when dependencies change. It's an optimization tool to avoid expensive calculations on every render.

Memoizing Expensive Calculations
import { useMemo, useState } from 'react';

function expensiveCalculation(num) {
  console.log('Calculating...');
  for (let i = 0; i < 1000000000; i++) {} // Simulate expensive operation
  return num * 2;
}

function Counter() {
  const [count, setCount] = useState(0);
  const [input, setInput] = useState('');

  // Only recalculates when 'count' changes, not when 'input' changes
  const doubled = useMemo(() => {
    return expensiveCalculation(count);
  }, [count]);

  return (
    <div>
      <input value={input} onChange={e => setInput(e.target.value)} />
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <p>Doubled: {doubled}</p>
    </div>
  );
}

When to Use useMemo

  • Expensive calculations that run on every render
  • Referential equality is important (passing objects to child components)
  • Filtering or sorting large arrays
  • Don't overuse - only for actual performance problems

useCallback

const memoizedCallback = useCallback(() => { doSomething(a, b) }, [a, b])

The useCallback hook returns a memoized callback function. It's useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders.

Memoizing Callbacks
import { useState, useCallback, memo } from 'react';

// Child component wrapped in React.memo
const Button = memo(({ onClick, children }) => {
  console.log('Button rendered');
  return <button onClick={onClick}>{children}</button>;
});

function Parent() {
  const [count, setCount] = useState(0);
  const [otherState, setOtherState] = useState(0);

  // Callback only recreated when count changes
  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <Button onClick={increment}>Increment</Button>
      <button onClick={() => setOtherState(otherState + 1)}>
        Other State
      </button>
    </div>
  );
}

Common Use Cases

  • Passing callbacks to memoized child components
  • Preventing unnecessary re-renders in child components
  • Dependencies in useEffect that shouldn't trigger re-runs
  • Event handlers in complex component trees

Custom Hooks

function useCustomHook() { /* logic */ return value; }

Custom hooks let you extract component logic into reusable functions. A custom hook is a JavaScript function whose name starts with "use" and that may call other hooks.

useLocalStorage Hook
import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  // Get from local storage or use initial value
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error);
      return initialValue;
    }
  });

  // Update local storage when value changes
  const setValue = (value) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.log(error);
    }
  };

  return [storedValue, setValue];
}

// Usage
function App() {
  const [name, setName] = useLocalStorage('name', 'Guest');
  
  return (
    <input value={name} onChange={e => setName(e.target.value)} />
  );
}
useFetch Hook
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        const json = await response.json();
        setData(json);
        setError(null);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

// Usage
function UserList() {
  const { data, loading, error } = useFetch('https://api.example.com/users');

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return <ul>{data.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}

Popular Custom Hooks

  • useLocalStorage - Sync state with localStorage
  • useFetch - Fetch data from APIs
  • useDebounce - Debounce state values
  • useWindowSize - Track window dimensions
  • useOnClickOutside - Detect clicks outside element

Quick Comparison

Hook Purpose Returns When to Use
useState Manage component state [state, setState] Simple state management
useEffect Handle side effects Cleanup function (optional) Data fetching, subscriptions, DOM updates
useContext Access context value Context value Global state, theme, auth
useReducer Manage complex state [state, dispatch] Multiple sub-values, complex logic
useRef Persist mutable values Ref object DOM access, storing values without re-render
useMemo Memoize expensive calculations Memoized value Performance optimization
useCallback Memoize callback functions Memoized function Prevent child re-renders