As React applications grow, components often become cluttered with repeated logic for fetching data, listening to browser events, authentication, responsive layouts, forms, timers, and much more.
React Custom Hooks solve this problem by allowing you to extract stateful logic into reusable functions without changing the component hierarchy.
Instead of copying the same useEffect, useState, and event listeners across multiple components, you encapsulate that behavior once and reuse it everywhere.
💡 Think of a Custom Hook as a reusable "behavior module" rather than a reusable UI component.
Prerequisites
You should be familiar with:
- Functional Components
useStateuseEffect- ES6 Modules
- Arrow Functions
- Basic TypeScript (optional)
Why Custom Hooks Exist
Imagine three different components:
- Navbar
- Sidebar
- Dashboard
Each needs the browser width.
Without Custom Hooks:
- duplicated state
- duplicated event listeners
- duplicated cleanup logic
This quickly becomes difficult to maintain.
With a Custom Hook:
Components
│
├── Navbar
├── Sidebar
└── Dashboard
│
▼
useWindowSize()
Each component consumes the same reusable logic.
What Is a Custom Hook?
A Custom Hook is simply a JavaScript (or TypeScript) function whose name starts with use and which internally uses one or more React Hooks.
function useSomething() {
const [value, setValue] = useState(0);
return value;
}
The use prefix is important because React's lint rules rely on it to validate correct Hook usage.
Rules of Hooks
Always follow these rules:
- ✅ Call Hooks only at the top level.
- ✅ Call Hooks only inside React components or other Hooks.
- ✅ Prefix every custom Hook with
use. - ❌ Never call Hooks inside loops.
- ❌ Never call Hooks inside conditions.
- ❌ Never call Hooks after an early return.
Building a Custom Hook
Example: useWindowSize
import { useEffect, useState } from "react";
type WindowSize = {
width: number;
height: number;
};
export function useWindowSize(): WindowSize {
const getSize = () => ({
width: window.innerWidth,
height: window.innerHeight,
});
const [size, setSize] = useState<WindowSize>(getSize);
useEffect(() => {
const handleResize = () => {
setSize(getSize());
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return size;
}
Usage
function Dashboard() {
const { width } = useWindowSize();
return <h1>{width}px</h1>;
}
Under the Hood
React does not create a new feature for Custom Hooks.
Internally:
- Your component renders.
- React calls
useWindowSize(). useWindowSize()callsuseState()anduseEffect().- React stores those Hooks in the same order as every render.
This is why Hook order must never change between renders.
Production Use Cases
Common custom Hooks include:
useFetchuseAuthuseLocalStorageuseDebounceuseOnlineStatususeThemeuseMediaQueryuseIntersectionObserveruseInfiniteScroll
These are widely used in enterprise React applications to reduce duplication and improve maintainability.
Performance Considerations
Although Custom Hooks improve code organization, they are not a performance optimization by themselves.
Consider:
- Avoid unnecessary state updates.
- Clean up subscriptions.
- Remove event listeners.
- Memoize expensive computations when appropriate.
- Debounce resize and scroll events if frequent updates are unnecessary.
Common Mistakes
- Calling Hooks conditionally.
- Forgetting cleanup functions.
- Returning unstable object references unnecessarily.
- Creating duplicate subscriptions.
- Using a Hook for one-off logic that doesn't need reuse.
- Performing expensive work on every render.
- Forgetting the
useprefix. - Mixing unrelated responsibilities into one Hook.
- Ignoring dependency arrays.
- Accessing
windowduring server-side rendering.
⚠️ SSR Tip: Guard browser-only APIs:
if (typeof window === "undefined") {
return { width: 0, height: 0 };
}
Best Practices
- Keep Hooks focused on a single responsibility.
- Return only what consumers need.
- Prefer TypeScript for reusable Hooks.
- Document inputs and outputs.
- Write unit tests for shared Hooks.
- Keep side effects predictable.
- Separate UI from business logic.
Interview Questions
Why should a Custom Hook start with use?
Because React's lint rules and ecosystem tooling recognize it as a Hook and validate proper usage.
Can two components share state using the same Custom Hook?
No. Each invocation gets its own isolated state unless the Hook intentionally connects to shared storage (such as Context or an external store).
Are Custom Hooks faster than duplicating logic?
Not inherently. Their primary benefits are reuse, maintainability, and consistency.
FAQ
Are Custom Hooks components?
No. They contain reusable logic, not UI.
Can a Custom Hook call another Custom Hook?
Yes.
Can Custom Hooks be asynchronous?
The Hook itself should remain synchronous, but it can manage asynchronous operations using useEffect.
Key Takeaways
- Custom Hooks extract reusable stateful logic.
- They improve maintainability and readability.
- Every Hook must follow the Rules of Hooks.
- They do not share state automatically.
- Production Hooks should handle cleanup, SSR, and edge cases.
Next Steps
Try implementing:
useDebounceuseLocalStorageuseFetchuseInfiniteScrolluseMediaQueryuseClipboard
Mastering these Hooks will significantly improve your React architecture skills and prepare you for senior frontend interviews.
