In short
When two components need the same logic, like fetching data or reading localStorage, don't copy-paste it. Pull it into a custom hook and reuse it like useState.
Your own reusable functions (prefixed `use...`) that extract and share stateful logic between components — the primary code-reuse pattern in modern React.

Think of it like this
Ghar ka masala mix
Mummy grinds her garam masala once and uses it in many dishes. Each dish gets its own spoonful, so adding it to the dal doesn't change the sabzi. A custom hook is that mix: the logic is shared, but every component gets its own separate state.
Watch it happen
useLocalStorage at work
("theme", "light")
1/6ThemeToggle calls useLocalStorage("theme", "light"), just like it would call useState.
The big ideas
What you need to know
Starts with use
Name it useSomething. That tells React and the linter that the hook rules apply inside.
Built from other hooks
A custom hook is just a function that calls useState, useEffect or other hooks and returns what you need.
State is not shared
Two components using useToggle each get their own toggle. To share state, lift it up or use Context.
Cleaner components
The component reads like a sentence, const { data, error } = usePosts(), with the details tucked away.
See it in code
Walk through the code
1import { useEffect, useState } from "react";2 3export function useLocalStorage<T>(key: string, initial: T) {4 const [value, setValue] = useState<T>(() => {5 const saved = localStorage.getItem(key);6 return saved !== null ? (JSON.parse(saved) as T) : initial;7 });8 9 useEffect(() => {10 localStorage.setItem(key, JSON.stringify(value));11 }, [key, value]);12 13 return [value, setValue] as const;14}15 16// const [theme, setTheme] = useLocalStorage("theme", "light");1/3The lazy initial state reads localStorage once, falling back to initial.
It works like useState but survives a page reload. In Next.js, use it only in client code, since localStorage doesn't exist on the server.
Save yourself the pain
Common mistakes
Oops
Naming it getUserData and calling hooks inside.
Do this instead
Start the name with use, like useUserData. Otherwise the linter can't check the hook rules for you.
Oops
Expecting two components that call the same custom hook to share data.
Do this instead
Each call gets fresh state. Shared data needs Context, lifted state or a store like Zustand.
Your mission
Write three tiny hooks
Quick check
Two components both call useCounter(). One of them clicks +1. What happens to the other?
Go deeper
- 1Extract a repeated `useState`+`useEffect` pattern into a custom hook.
- 2Build `useLocalStorage` or `useDebounce` as a practice custom hook.
- 3Learn that custom hooks are just functions — they don't share state between usages.
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.