
Building Custom Hooks in React

One of the most useful ideas in modern React is that stateful logic can be reused without reusing markup.
That is the real value of custom hooks.
Before hooks, reusing stateful behaviour often pushed developers towards patterns like higher‑order components or render props. Those patterns still have their place, but custom hooks made a common problem much easier to solve. If several components need the same stateful behaviour, we can extract that behaviour into a hook and call it from each component.
The important phrase there is stateful behaviour, not UI.
A Custom Hook is Just a Function That Uses Hooks
At its simplest:
const useToggle = ( initialValue = false,): [boolean, () => void] => { const [value, setValue] = useState(initialValue); const toggle = (): void => { setValue((currentValue) => !currentValue); }; return [value, toggle];};Now any component can use that logic:
const Panel = (): JSX.Element => { const [isOpen, toggleOpen] = useToggle(false); return <button onClick={toggleOpen}>{isOpen ? 'Hide' : 'Show'}</button>;};That is a small example, but it shows the pattern clearly.
The Hook is Sharing Logic, Not State
This distinction matters a great deal.
If two components both call useToggle(), they do not magically receive the same shared toggle state. Each component gets its own separate stateful instance of that logic.
Custom hooks are like extracted behaviour templates. They encapsulate repeated stateful code, but each caller still owns its own state.
That is why custom hooks are not a replacement for context or shared application state. They solve a different problem.
A Realistic Example: Window Width
Suppose several components need to respond to viewport width. Repeating the same effect and event listener in each component would be clumsy.
That logic can be extracted:
const useWindowWidth = (): number => { const [width, setWidth] = useState(window.innerWidth); useEffect(() => { const handleResize = (): void => { setWidth(window.innerWidth); }; window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); }; }, []); return width;};Now components can simply ask for the width:
const Layout = (): JSX.Element => { const width = useWindowWidth(); return <p>Viewport width: {width}</p>;};The stateful logic lives in one place, and the consuming component stays focused on rendering.
Good Custom Hooks Usually Do One Coherent Job
The strongest hooks are often narrow:
- detect the window width
- manage a toggle
- keep the document title in sync
- store the previous value
- manage a fetch request
When a hook starts handling three unrelated concerns at once, it often stops being reusable and starts becoming a mini‑framework hidden inside a function.
Naming matters because the use prefix is not cosmetic
Hooks are expected to start with use for a reason. The name tells React tooling and human readers that hook rules apply.
So:
useWindowWidthuseToggleusePrevious
are good names.
Calling the same function getWindowWidthState would obscure what it really is.
Custom Hooks Help Remove Duplication from Effects
This is one of the places they shine most.
Effects often bring:
- setup
- teardown
- subscriptions
- timers
- DOM listeners
When that same pattern appears in multiple components, extracting it into a custom hook can make the code much easier to trust. Instead of repeating the effect and hoping every copy stays in sync, we centralise the behaviour.
They Also Make Component Bodies Easier to Read
Consider a component doing this inline:
- state for whether data is loading
- state for the result
- state for the error
- an effect to fetch the data
- cleanup logic
That is a lot of plumbing mixed in with markup.
If that behaviour becomes a hook such as useProducts(), the component body becomes more declarative:
const ProductList = (): JSX.Element => { const { products, isLoading, error } = useProducts(); if (isLoading) { return <p>Loading...</p>; } if (error) { return <p>Could not load products.</p>; } return ( <ul> {products.map((product) => ( <li key={product.id}>{product.name}</li> ))} </ul> );};The component now reads more like UI, and the stateful mechanics move behind a clearer abstraction.
A Hook Should Expose a Useful Interface, Not Every Internal Detail
This is where design judgement enters.
A good custom hook returns the values and functions the consuming component actually needs, not a random dump of internal implementation pieces.
That may mean returning:
- a tuple
- an object
- a small set of callbacks
The shape should reflect how the hook is meant to be used.
The Rules of Hooks Still apply
Because a custom hook is still using hooks internally, the normal rules remain:
- call hooks at the top level
- do not call them conditionally
- only call hooks from React components or other hooks
Custom hooks are not loopholes around those rules. They are one of the intended ways to compose them.
Not Every Repeated Code Block Needs to Become a Custom Hook
If logic is purely synchronous and stateless, a plain helper function may be enough.
The hook pattern is most useful when the repeated logic genuinely involves React's stateful or effectful features. Otherwise, wrapping ordinary utility code in a hook just because it lives near React can make the design more confusing.
Custom Hooks are One of React's Cleaner Reuse Mechanisms
That is why they became popular so quickly.
They let us take repeated stateful behaviour and give it a name, an interface, and a home of its own. The component consuming the hook becomes easier to read, and the logic becomes easier to reuse and evolve.
The main thing to remember is what problem they solve. Custom hooks do not share state globally, and they do not render UI for us. They package recurring React logic in a way that feels natural inside function components.
Used that way, they are one of the most practical additions React has made.
Related Articles

What is a Static Site Generator? 
Building a Custom Vue 3 Hook Using the Composition API. Building a Custom Vue 3 Hook Using the Composition API

Track Element Visibility Using Intersection Observer. Track Element Visibility Using Intersection Observer

Understanding Phantom window.resize Events in iOS. Understanding Phantom
window.resizeEvents in iOS
Position: sticky in CSS. position: stickyin CSS
The Execution Context in JavaScript. The Execution Context in JavaScript

Understanding the Backtracking Approach: Solving the 'Word Search' Problem. Understanding the Backtracking Approach: Solving the 'Word Search' Problem

Enhancing Web Typography with text‑wrap: balance. Enhancing Web Typography with
text‑wrap: balance
Building Design Systems for Web Applications with Figma, Storybook, and npm. Building Design Systems for Web Applications with Figma, Storybook, and npm

Simplify Asynchronous JavaScript with async/await. Simplify Asynchronous JavaScript with
async/await
Understanding the :hover Pseudo‑Class in CSS. Understanding the
:hoverPseudo‑Class in CSS
Using Regex to Replace Numbers in a String. Using Regex to Replace Numbers in a String