⚛️ React Hooks Cheat Sheet

📊 Quick Reference Table

Hook Purpose When to Use Return Value
useState Manage local state Component needs to track data [state, setState]
useEffect Side effects API calls, subscriptions, DOM updates void (cleanup function)
useContext Consume context Access global state/theme/auth Context value
useReducer Complex state logic Multiple state updates together [state, dispatch]
useCallback Memoize functions Prevent unnecessary re-renders Memoized function
useMemo Memoize values Expensive calculations Memoized value
useRef Mutable ref object DOM access, persist values Ref object
useLayoutEffect Sync layout effects DOM measurements void (cleanup function)
useImperativeHandle Expose ref methods Parent needs child methods void
useDebugValue DevTools label Custom hooks debugging void

🎯 Core Hooks

useState

Manages local component state

const [state, setState] = useState(initialValue);
// Simple counter
const [count, setCount] = useState(0);

// Object state
const [user, setUser] = useState({
  name: '',
  email: ''
});

// Lazy initial state
const [data, setData] = useState(() => {
  return expensiveComputation();
});

💡 Tips:

  • State updates are asynchronous
  • Use functional updates for dependent state
  • State updates trigger re-renders

useEffect

Handles side effects in components

useEffect(() => {
  // Effect logic
  return () => {
    // Cleanup (optional)
  };
}, [dependencies]);
// API call on mount
useEffect(() => {
  fetchData();
}, []); // Empty array = run once

// Watch for changes
useEffect(() => {
  console.log('User changed:', user);
}, [user]);

// Cleanup subscription
useEffect(() => {
  const subscription = subscribe();
  return () => subscription.unsubscribe();
}, []);

💡 Tips:

  • Empty deps [] = componentDidMount
  • No deps = runs after every render
  • Return cleanup function to prevent memory leaks

useContext

Consumes React context values

const value = useContext(MyContext);
// Theme context
const ThemeContext = createContext();

// Provider component
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Child />
    </ThemeContext.Provider>
  );
}

// Consumer component
function Child() {
  const theme = useContext(ThemeContext);
  return <div className={theme}>...</div>;
}

💡 Tips:

  • Component re-renders when context changes
  • Use multiple contexts for separation
  • Consider performance for frequent updates

useReducer

Complex state management with actions

const [state, dispatch] = useReducer(reducer, initialState);
const initialState = { count: 0 };

function 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();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(
    reducer, 
    initialState
  );
  
  return (
    <button onClick={() => dispatch({type: 'increment'})}>
      Count: {state.count}
    </button>
  );
}

💡 Tips:

  • Better than useState for complex logic
  • Actions make state updates predictable
  • Great for multiple related state values

⚡ Performance Hooks

useCallback

Memoizes callback functions

const memoizedCallback = useCallback(
  () => { /* function */ },
  [dependencies]
);
// Prevent child re-renders
const handleClick = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

// Optimized event handler
const SearchBar = ({ onSearch }) => {
  const [query, setQuery] = useState('');
  
  const handleSearch = useCallback(() => {
    onSearch(query);
  }, [query, onSearch]);
  
  return (
    <>
      <input onChange={e => setQuery(e.target.value)} />
      <button onClick={handleSearch}>Search</button>
    </>
  );
};

💡 Tips:

  • Use with React.memo for optimization
  • Don't overuse - has overhead
  • Essential for expensive child components

useMemo

Memoizes expensive computations

const memoizedValue = useMemo(
  () => computeExpensiveValue(a, b),
  [a, b]
);
// Expensive calculation
const expensiveValue = useMemo(() => {
  return items.reduce((sum, item) => {
    return sum + item.value * item.quantity;
  }, 0);
}, [items]);

// Filter/sort optimization
const sortedList = useMemo(() => {
  return [...items].sort((a, b) => b.price - a.price);
}, [items]);

// Complex object creation
const chartData = useMemo(() => ({
  labels: data.map(d => d.label),
  values: data.map(d => d.value),
  colors: generateColors(data.length)
}), [data]);

💡 Tips:

  • Only use for expensive operations
  • Profile before optimizing
  • Dependencies must be exhaustive

📌 Ref Hooks

useRef

Creates mutable ref object

const refContainer = useRef(initialValue);
// DOM element reference
const inputRef = useRef(null);

useEffect(() => {
  inputRef.current.focus();
}, []);

return <input ref={inputRef} />;

// Persist value across renders
const renderCount = useRef(0);
renderCount.current++;

// Store previous value
function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

💡 Tips:

  • .current persists for full lifetime
  • Doesn't trigger re-renders
  • Useful for storing mutable values

useImperativeHandle

Customizes ref exposed to parent

useImperativeHandle(ref, () => ({
  // exposed methods
}), [deps]);
// Child component
const FancyInput = forwardRef((props, ref) => {
  const inputRef = useRef();
  
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    clear: () => {
      inputRef.current.value = '';
    }
  }));
  
  return <input ref={inputRef} />;
});

// Parent component
const parentRef = useRef();
parentRef.current.focus();
parentRef.current.clear();

💡 Tips:

  • Use sparingly - breaks encapsulation
  • Always use with forwardRef
  • Good for form libraries

📐 Layout Hooks

useLayoutEffect

Synchronous DOM mutations

useLayoutEffect(() => {
  // Synchronous effect
}, [deps]);
// Measure DOM element
function MeasureExample() {
  const [height, setHeight] = useState(0);
  const divRef = useRef();
  
  useLayoutEffect(() => {
    setHeight(divRef.current.getBoundingClientRect().height);
  });
  
  return (
    <div ref={divRef}>
      Height: {height}px
    </div>
  );
}

// Prevent flicker
useLayoutEffect(() => {
  // DOM updates here won't cause visual flicker
  element.style.opacity = 1;
});

💡 Tips:

  • Fires synchronously after DOM mutations
  • Blocks visual updates
  • Use useEffect unless you need sync

useDebugValue

Display label in React DevTools

useDebugValue(value, formatFn?);
// Custom hook with debug info
function useUser(userId) {
  const [user, setUser] = useState(null);
  
  // Display in DevTools
  useDebugValue(user ? `User: ${user.name}` : 'Loading...');
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);
  
  return user;
}

// With formatter
useDebugValue(date, date => date.toDateString());

💡 Tips:

  • Only visible in React DevTools
  • Use for custom hooks
  • Format function for expensive formatting

🔧 Common Custom Hooks Patterns

useLocalStorage

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });
  
  const setValue = value => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };
  
  return [storedValue, setValue];
}

useFetch

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);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, [url]);
  
  return { data, loading, error };
}

useToggle

function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);
  
  const toggle = useCallback(() => {
    setValue(v => !v);
  }, []);
  
  return [value, toggle];
}

// Usage
const [isOpen, toggleOpen] = useToggle();
const [isDark, toggleTheme] = useToggle(true);

useDebounce

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => clearTimeout(handler);
  }, [value, delay]);
  
  return debouncedValue;
}

// Usage
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearch = useDebounce(searchTerm, 500);

📚 Rules of Hooks & Best Practices

🎨 Common Hook Patterns

Pattern Code Example Use Case
Conditional Effects useEffect(() => { if (condition) { /* effect */ } }, [condition]) Run effect based on condition
Previous Value const prevValue = useRef(); useEffect(() => { prevValue.current = value }) Track previous prop/state
Mount Only useEffect(() => { /* once */ }, []) Run once on mount
Unmount Cleanup useEffect(() => { return () => { /* cleanup */ } }, []) Cleanup on unmount
Force Update const [, forceUpdate] = useReducer(x => x + 1, 0) Force re-render
Safe State Update setState(prev => prev + 1) Update based on previous
Lazy Initial State useState(() => expensiveOperation()) Expensive initial value