Suggest an editImprove this articleRefine the answer for “What are custom hooks in React”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Custom hooks** are JavaScript functions that start with "use" and call other React hooks to share stateful logic across components. ```jsx function useUser(userId) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { fetch(`/api/users/${userId}`).then(r => r.json()).then(setUser) .finally(() => setLoading(false)); }, [userId]); return { user, loading }; } ``` **Key point:** the "use" prefix tells React to enforce hook rules: call at the top level only, never inside conditions or callbacks.Shown above the full answer for quick recall.Answer (EN)Image**Custom hooks** are JavaScript functions that start with "use" and call other React hooks to share stateful logic across components. ## Theory ### TL;DR - Think of a custom hook like a shared recipe: write the `useState` + `useEffect` logic once, any component grabs it without rewriting - Main difference from a regular function: custom hooks run during React's render phase, so state and effects stay synced with re-renders - Decision rule: if the same hook logic appears in 2+ components or grows past ~20 lines, extract it - If a function doesn't call any hooks, it's just a function. No "use" prefix needed. ### Quick example ```jsx // Before: resize logic duplicated in every component that needs window width function WindowSize() { const [width, setWidth] = useState(window.innerWidth); useEffect(() => { const handleResize = () => setWidth(window.innerWidth); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return <div>Width: {width}px</div>; } // After: extract once, reuse anywhere function useWindowSize() { const [width, setWidth] = useState(window.innerWidth); useEffect(() => { const handleResize = () => setWidth(window.innerWidth); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); // cleanup on unmount }, []); return width; } function WindowSize() { const width = useWindowSize(); // component stays clean return <div>Width: {width}px</div>; } ``` That is the whole pattern. The hook moves out, the component gets simpler. ### How custom hooks differ from regular functions A regular function can be called anywhere: inside loops, conditionals, callbacks. A custom hook cannot. React tracks hook calls in a fixed order inside each component's fiber node via a linked list of states and effects. Call hooks conditionally and that list shifts, so React reads the wrong state from the wrong slot. This is why the "use" prefix matters. React sees it and enforces the Rules of Hooks: call at the top level only, and only from components or other hooks. A function without "use" gets no such treatment and its internal state won't persist between renders. ### When to use - API fetch + loading/error state repeated in multiple components: custom hook - Form validation and submission logic shared across pages: custom hook - Syncing state with localStorage: custom hook - A one-off date formatter with no state: plain function - A UI animation used in exactly one component: keep it inline ### How React handles it internally React processes custom hooks during the render phase using the same mechanism as built-in hooks. Each hook call maps to a slot in the fiber's linked list through `mountState` and `updateState` dispatchers. On re-render, React replays calls in the same order and restores state from those slots. There is no V8 magic here. It is React's reconciler enforcing order. That is also why composition works freely: `useUser` can call `useFetch`, which calls `useState` and `useEffect`. All of them share the same component fiber. ### Common mistakes **Forgetting the "use" prefix:** ```jsx function getWindowSize() { // React skips hook rules here const [width, setWidth] = useState(window.innerWidth); // state won't persist } ``` Without the prefix React treats this as a plain function. State tracking is skipped entirely. Fix: rename to `useWindowSize`. **Calling a hook conditionally:** ```jsx function Component({ show }) { if (show) { const [data, setData] = useState(null); // hook order shifts when show changes } } ``` This causes "Invalid hook call" or unpredictable state bugs. Move every hook call to the top level of the function, always. **Stale closure in a fetch hook:** ```jsx function useAsyncTask(callback) { const [result, setResult] = useState(null); useEffect(() => { callback().then(setResult); }, [callback]); // callback must be stable or this fires every render return result; } // Wrong: callback recreated each render, triggers infinite loop function BadComponent() { const [count, setCount] = useState(0); const result = useAsyncTask(async () => { return count; // captures stale count from first render only }); } // Fix: stabilize with useCallback const callback = useCallback(async () => { return count; }, [count]); const result = useAsyncTask(callback); ``` This is the most common production issue with custom hooks. ESLint's `react-hooks/exhaustive-deps` catches it automatically. **Wrapping trivial logic in a hook:** ```jsx function useDouble(n) { return n * 2; } // no hooks inside, no point ``` If there are no hook calls inside, it is just a function. Skip the "use" prefix and the extra indirection. ### Real-world usage - TanStack Query: `useQuery` composes `useEffect` + `useState` for fetching with caching - React Hook Form: `useForm` packages all form state and validation into one call - SWR (used in Next.js): `useSWR` handles fetch, caching, and revalidation - Zustand: `useStore` acts as a lightweight hook for global state Custom hooks replaced higher-order components and render props for sharing stateful logic. Libraries like TanStack Query show how far the pattern can scale. ### Follow-up questions **Q:** Why must custom hook names start with "use"? **A:** React scans for the "use" prefix during render to apply the Rules of Hooks. Without it, the function is treated as a regular function, state doesn't persist between renders, and effects won't run correctly. **Q:** Can a custom hook call other custom hooks? **A:** Yes. They compose freely. `useUser` can call `useFetch`, which calls `useState` and `useEffect`. All of them share the same component's fiber node. **Q:** What happens if you call a custom hook inside a callback? **A:** React throws "Invalid hook call". Hooks must be called at the top level of a function component or another hook, not inside event handlers or async functions. **Q:** What is the difference between a custom hook and `useReducer`? **A:** `useReducer` manages complex local state inside one component. A custom hook packages state logic so multiple components can share it without duplicating code. **Q:** Explain a stale closure bug in a custom fetch hook. **A:** If the `useEffect` dependency array omits a prop like `userId`, the effect captures the value from the first render. When the component receives a new `userId`, the effect does not re-run and you get data for the wrong user. Fix: include all dependencies in the array, or stabilize the callback reference with `useCallback`. ## Examples ### Basic: tracking window width ```jsx function useWindowSize() { const [width, setWidth] = useState(window.innerWidth); useEffect(() => { const handleResize = () => setWidth(window.innerWidth); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); // cleanup on unmount }, []); return width; } function Header() { const width = useWindowSize(); return <nav>{width < 768 ? <MobileMenu /> : <DesktopMenu />}</nav>; } ``` One hook, many components. Any component that needs window width calls `useWindowSize()` instead of re-implementing the listener logic from scratch. ### Intermediate: data fetching with loading and error states ```jsx // useUser.js - shared across dashboards, profile pages, admin panels function useUser(userId) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { setLoading(true); fetch(`/api/users/${userId}`) .then(res => res.json()) .then(setUser) .catch(setError) .finally(() => setLoading(false)); }, [userId]); // re-fetches automatically when userId changes return { user, loading, error }; } function Profile({ userId }) { const { user, loading, error } = useUser(userId); if (loading) return <div>Loading...</div>; if (error) return <div>Failed to load user</div>; return <div>{user.name}</div>; } ``` The component only handles rendering. All fetch logic lives in the hook. I've seen the missing `[userId]` dep cause hours of debugging in production where switching between users showed stale data - that one dependency array entry fixes it.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.