Building Your First Custom Hook
In this page:
What Problem Custom Hooks Solve
When two different components need the same piece of stateful logic, like tracking window size or a counter, copying that logic into both components duplicates code and makes updates error-prone. A custom hook lets you write that logic once as a reusable function. Any component can then call it to get the same behavior.
Note: If you find yourself copying a useState+useEffect pair into a second component, that's usually a sign it should become a custom hook.
Warning: A custom hook shares its LOGIC, not its STATE — two components calling the same custom hook still get independent, separate state.
Example: What Problem Custom Hooks Solve
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function useCounter(start) {
const [count, setCount] = React.useState(start);
const increment = () => setCount(c => c + 1);
return { count, increment };
}
function App() {
const { count, increment } = useCounter(0);
return <button onClick={increment}>Count: {count}</button>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
The 'use' Naming Convention
A custom hook's name must start with lowercase use, like useCounter or useFetch. This isn't just a style choice — React's linter and the rules-of-hooks system use this prefix to know a function is allowed to call other hooks inside it. A regular helper function without the use prefix cannot call useState or useEffect safely.
Note: Name your hook after what it returns or does, like useWindowWidth or useOnlineStatus, so it's self-documenting.
Warning: Naming a function useHelper() when it doesn't actually call any hooks is misleading — only name it 'use...' if it genuinely uses hooks internally.
Example: The 'use' Naming Convention
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function useToggleValue(initial) {
const [value, setValue] = React.useState(initial);
const toggle = () => setValue(v => !v);
return [value, toggle];
}
function App() {
const [isOn, toggle] = useToggleValue(false);
return <button onClick={toggle}>{isOn ? "ON" : "OFF"}</button>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Building Your First Custom Hook
To build a custom hook, write a normal JavaScript function, call one or more built-in hooks inside it, and return whatever data or functions the calling component needs. This example builds useWindowWidth, which tracks the browser window's width and updates automatically on resize.
Note: Return an array (like useState does) when order matters, or an object when you want named, self-documenting values.
Warning: Always clean up any event listeners you add inside a custom hook's useEffect, or they'll pile up every time the component re-renders.
Example: Building Your First Custom Hook
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function useWindowWidth() {
const [width, setWidth] = React.useState(window.innerWidth);
React.useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return width;
}
function App() {
const width = useWindowWidth();
return <p>Window width: {width}px</p>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
- Forgetting to prefix the function name with use, which breaks React's rules-of-hooks linting and hook detection.
- Copy-pasting the same useState+useEffect logic into multiple components instead of extracting it into a custom hook.
- Calling a custom hook conditionally (inside an if-statement), which violates the same rules that apply to built-in hooks.
- A custom hook is a JavaScript function whose name starts with use and that calls other hooks inside it.
- Custom hooks let you extract and reuse stateful logic across multiple components.
- They follow the same rules of hooks as built-in hooks (top-level calls only, no conditionals).
- Each component that uses a custom hook gets its own independent state, not a shared one.
Available since React 16.8 (Hooks introduction) — custom hooks are just plain functions built on top of built-in hooks.
Chapter Quiz — Complete all 10 topics to unlock
0/10 topics done
Complete these topics first: