React Hooks đŸĒ

1. Introduction 👋

Hooks transformed how developers write React applications by allowing function components to use state, side effects, and other React features without writing a class. This tutorial provides a comprehensive tour of React's built-in Hooks, custom Hooks, and the rules that govern how they work.

Information

This tutorial assumes familiarity with Components, State, and Event Handling, covered in earlier tutorials.

2. What are Hooks? 🤔

Hooks are special functions, always prefixed with use, that let function components "hook into" React features like state, context, and lifecycle behavior.

Code Snippet

import { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => setSeconds((s) => s + 1), 1000);
    return () => clearInterval(interval);
  }, []);

  return <p>Elapsed: {seconds}s</p>;
}
>>"Hooks let you use different React features from your components." — React Documentation

3. Why Hooks? 💡

  • Simpler Components: No need for class syntax, this binding, or constructors.
  • Reusable Logic: Custom Hooks let you extract and share stateful logic between components.
  • Better Organization: Related logic can be grouped together, instead of split across lifecycle methods.
  • Easier Testing: Function components with Hooks are generally simpler to test in isolation.

4. History of Hooks 📜

5. Rules of Hooks âš–ī¸

Hooks rely on being called in a consistent order on every render, so React must follow strict rules to track each Hook's state correctly.

  1. Only call Hooks at the top level of a function component or custom Hook.
  2. Never call Hooks inside loops, conditions, or nested functions.
  3. Only call Hooks from React function components or other custom Hooks — never from regular JavaScript functions.

❌ Invalid — Hook Inside a Condition

function Bad({ isLoggedIn }) {
  if (isLoggedIn) {
    const [user, setUser] = useState(null); // ❌ Breaks the rules of Hooks
  }
}

Danger

Violating the Rules of Hooks causes React to lose track of which state belongs to which Hook call, leading to unpredictable bugs and state mismatches across renders.

6. Hook Naming Convention đŸˇī¸

Every Hook — built-in or custom — must start with the prefix use. This convention allows both React and linting tools to recognize and enforce the Rules of Hooks.

Code Snippet

// Built-in Hooks
useState(), useEffect(), useContext()

// Custom Hooks
useAuth(), useFetch(), useLocalStorage()

Best Practice

Install the eslint-plugin-react-hooks package to automatically catch Rules of Hooks violations during development.

7. Built-in Hooks Overview 📚

CategoryHooks
StateuseState, useReducer
EffectuseEffect, useLayoutEffect, useInsertionEffect
ContextuseContext
RefuseRef, useImperativeHandle
PerformanceuseMemo, useCallback
ConcurrentuseTransition, useDeferredValue
Escape HatchuseDebugValue, useId, useSyncExternalStore
Server / Actionsuse, useActionState, useOptimistic

8. State Hooks 🧠

Code Snippet

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

Manages a single piece of state, ideal for simple values.

Code Snippet

function reducer(state, action) {
  switch (action.type) {
    case "increment": return { count: state.count + 1 };
    case "decrement": return { count: state.count - 1 };
    default: return state;
  }
}

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

Manages complex state logic with multiple sub-values or transitions, similar to a mini Redux reducer.

Tip

Reach for useReducer when state updates involve multiple related values or complex transition logic that's hard to express with several useState calls.

9. Effect Hooks 🔄

Effect Hooks let components synchronize with external systems — APIs, subscriptions, timers, or the DOM — outside of React's normal rendering flow.

Code Snippet

useEffect(() => {
  const controller = new AbortController();

  fetch('/api/data', { signal: controller.signal })
    .then((res) => res.json())
    .then(setData);

  return () => controller.abort(); // cleanup
}, []); // dependency array
HookTiming
useEffectRuns after the browser paints the screen
useLayoutEffectRuns synchronously before the browser paints
useInsertionEffectRuns before DOM mutations, mainly for CSS-in-JS libraries

Warning

Always clean up subscriptions, timers, and event listeners in the cleanup function returned from useEffect, or you risk memory leaks.

10. Context Hooks 🌐

useContext lets a component read values from a Context Provider higher up the tree, without manually passing props through every intermediate level.

Code Snippet

const ThemeContext = createContext("light");

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Click Me</button>;
}

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

Note

useContext re-renders the consuming component whenever the Provider's value changes, so avoid passing large, frequently-changing objects when performance matters.

11. Ref Hooks 📌

useRef creates a mutable container that persists across renders without triggering a re-render when it changes — commonly used to access DOM nodes directly.

Code Snippet

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

  function focusInput() {
    inputRef.current.focus();
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus Input</button>
    </>
  );
}

Tip

Use useImperativeHandle alongside forwardRef to customize exactly what a parent can access via a child's ref.

12. Performance Hooks ⚡

Code Snippet

const sortedList = useMemo(
  () => [...items].sort((a, b) => a.value - b.value),
  [items]
);

Memoizes an expensive computed value, recalculating only when its dependencies change.

Code Snippet

const handleClick = useCallback(() => {
  console.log("Clicked:", id);
}, [id]);

Memoizes a function reference itself, useful when passing callbacks to memoized child components.

Caution

Avoid wrapping every value or function in useMemo/useCallback — the memoization overhead can outweigh the benefit for cheap computations.

13. Concurrent Hooks 🌊

Introduced with Concurrent React, these Hooks help keep the UI responsive during expensive updates by controlling update priority.

useTransition

function SearchResults() {
  const [isPending, startTransition] = useTransition();
  const [query, setQuery] = useState("");

  function handleChange(e) {
    const value = e.target.value;
    startTransition(() => {
      setQuery(value); // marked as a low-priority update
    });
  }

  return (
    <>
      <input onChange={handleChange} />
      {isPending && <span>Updating...</span>}
    </>
  );
}

Information

useDeferredValue serves a similar purpose to useTransition but is applied to a value rather than wrapping a state update function.

14. Escape Hatch Hooks đŸšĒ

HookPurpose
useIdGenerates unique, stable IDs for accessibility attributes across server and client renders.
useSyncExternalStoreSafely subscribes to external, non-React state sources (e.g., browser APIs, third-party stores).
useDebugValueDisplays a custom label for a custom Hook in React Developer Tools.

Code Snippet

function FormField({ label }) {
  const id = useId();
  return (
    <>
      <label htmlFor={id}>{label}</label>
      <input id={id} />
    </>
  );
}

15. Server Hooks đŸ–Ĩī¸

React 19 introduced Hooks designed for working with Server Components, async data, and form Actions.

use()

function Profile({ userPromise }) {
  const user = use(userPromise); // unwraps a Promise
  return <h1>{user.name}</h1>;
}

useActionState

function SubscribeForm() {
  const [state, formAction] = useActionState(subscribeAction, { error: null });

  return (
    <form action={formAction}>
      <input name="email" type="email" />
      <button type="submit">Subscribe</button>
      {state.error && <p>{state.error}</p>}
    </form>
  );
}

Note

Unlike other Hooks, use() can be called conditionally, making it an exception to the standard Rules of Hooks in specific scenarios.

16. Experimental Hooks đŸ§Ē

React occasionally ships Hooks under experimental or newly stabilized status, refining APIs based on real-world feedback before wide adoption.

  • useOptimistic — Shows an optimistic UI state while an async action is still in progress.
  • useFormStatus — Reads the pending status of the nearest parent <form>.

Caution

Experimental or recently stabilized Hooks may still evolve — always check the official React documentation for the latest guidance before relying on them heavily in production.

17. Custom Hooks đŸ› ī¸

A custom Hook is simply a JavaScript function, prefixed with use, that calls other Hooks internally to encapsulate and share reusable logic.

useLocalStorage.js

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

// Usage
function App() {
  const [theme, setTheme] = useLocalStorage("theme", "light");
}

18. Hook Composition 🧩

Custom Hooks can call other custom Hooks, allowing complex logic to be built up from smaller, focused pieces.

Code Snippet

function useAuthenticatedFetch(url) {
  const { token } = useAuth();
  const data = useFetch(url, {
    headers: { Authorization: `Bearer ${token}` },
  });
  return data;
}

Tip

Composing small, single-purpose Hooks together mirrors the same principle as composing small components — it keeps each piece easy to test and reason about.

19. Sharing Logic with Hooks 🔗

Before Hooks, sharing stateful logic between components required patterns like higher-order components or render props, which often led to deeply nested component trees. Custom Hooks solve this far more elegantly.

Old PatternHooks Equivalent
Higher-Order ComponentsCustom Hooks with shared logic
Render PropsCustom Hooks returning values directly
Mixins (legacy)Custom Hooks composed together

20. Hook Execution Order đŸ”ĸ

React relies on Hooks being called in the exact same order on every render to correctly associate each Hook call with its internal state.

Code Snippet

function Component() {
  const [a, setA] = useState(0);  // Hook 1
  const [b, setB] = useState(0);  // Hook 2
  useEffect(() => {}, []);         // Hook 3
  // Order must remain identical on every render
}

Important

This is precisely why Hooks cannot be called conditionally or inside loops — doing so would shift the call order and corrupt React's internal state tracking.

21. Hook Lifecycle âŗ

Function components don't have explicit lifecycle methods, but useEffect can replicate similar behavior by combining its dependency array with a cleanup function.

Class LifecycleHook Equivalent
componentDidMountuseEffect(() => {...}, [])
componentDidUpdateuseEffect(() => {...}, [dep])
componentWillUnmountCleanup function returned from useEffect

22. Hook Dependency Rules 📋

Hooks like useEffect, useMemo, and useCallback accept a dependency array that tells React when to re-run the logic.

Code Snippet

useEffect(() => {
  console.log("Runs on every render");
});

useEffect(() => {
  console.log("Runs only once, on mount");
}, []);

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

Warning

Omitting a value that the effect actually uses from the dependency array creates a stale closure bug — the effect keeps referencing an outdated value.

23. Common Hook Patterns 🎨

  • Data Fetching: useFetch(url) encapsulating loading, error, and data state.
  • Debouncing: useDebounce(value, delay) for search inputs.
  • Media Queries: useMediaQuery(query) for responsive logic in JS.
  • Previous Value Tracking: usePrevious(value) using useRef internally.
  • Toggle State: useToggle(initialValue) for simple boolean flags.

A Simple useToggle Hook

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

24. Hook Limitations âš ī¸

  • Hooks cannot be used inside class components.
  • Hooks cannot be called conditionally or inside loops (with the notable exception of use()).
  • Custom Hooks share logic, not state — each component calling a custom Hook gets its own independent state.
  • Overusing Hooks like useEffect for non-side-effect logic can lead to unnecessary complexity.

Note

A common misconception is that custom Hooks share state between components — in reality, each call creates a completely separate, independent instance of that state.

25. Hook Best Practices 🌟

  1. Follow the Rules of Hooks strictly, enforced via eslint-plugin-react-hooks.
  2. Keep useEffect dependency arrays accurate and complete.
  3. Extract reusable logic into custom Hooks rather than duplicating it across components.
  4. Prefer useReducer over multiple useState calls for complex, interrelated state.
  5. Avoid overusing useMemo/useCallback for trivial computations.
  6. Always clean up effects that create subscriptions, timers, or listeners.

26. Common Hook Mistakes đŸšĢ

  • Calling Hooks conditionally or inside loops, violating the Rules of Hooks.
  • Omitting dependencies from useEffect's array, causing stale closures.
  • Forgetting cleanup functions, leading to memory leaks from lingering subscriptions or timers.
  • Using useEffect for logic that could be computed directly during render (derived state).
  • Creating infinite render loops by updating state inside useEffect without proper dependencies.

Danger

Updating state inside useEffect based on that same state, without a proper dependency array, can trigger an infinite re-render loop that crashes the browser tab.

27. Frequently Asked Questions ❓

Question

Can I use Hooks in class components?

Answer

No. Hooks are exclusively available in function components and other custom Hooks — class components must continue using lifecycle methods.

Question

Do custom Hooks share state between components?

Answer

No. Each component that calls a custom Hook gets its own independent copy of that state — custom Hooks share logic, not the state itself.

Question

Why does my useEffect run twice in development?

Answer

In StrictMode, React intentionally double-invokes effects in development to help surface missing cleanup logic — this does not happen in production builds.

28. Summary 📝

Hooks unified how React components manage state, side effects, and shared logic, replacing older patterns like class lifecycle methods and higher-order components. From foundational Hooks like useState and useEffect to advanced concurrent and server-oriented Hooks, mastering this system unlocks the full power of modern React development.

Summary

With Hooks covered comprehensively, natural next steps include deep dives into useEffect and Side Effects, Context API, and Building Custom Hooks for specific real-world use cases.