FrontendReactHooksJavaScriptComponent

React Custom Hooks: Build Reusable Logic for Any Project

Custom hooks let you extract and share stateful logic across components without any extra component nesting. This guide walks through building five practical hooks you can use immediately.

Abdur Razzak

Abdur Razzak

Full-Stack Web Developer

May 26, 2026 9 min read

Custom hooks are one of the most powerful patterns available in React development. They allow you to extract component logic into reusable functions that can be shared across multiple components without changing the component hierarchy or introducing wrapper components. Before hooks were introduced in React 16.8, sharing stateful logic required patterns like higher-order components or render props, both of which added complexity and unnecessary nesting to the component tree. Custom hooks solve this problem elegantly by letting you encapsulate any combination of built-in hooks into a single function with a clear, focused purpose. Any function whose name starts with the word use and that calls other React hooks qualifies as a custom hook. The React linter enforces the Rules of Hooks on these functions automatically, catching bugs at compile time rather than at runtime.

The Rules Every Custom Hook Must Follow

Custom hooks follow exactly the same rules as built-in React hooks. They must be called at the top level of a React function component or another custom hook, and never inside loops, conditions, or nested functions. This constraint exists because React tracks the order in which hooks are called on every render to correctly associate state and effects with the right component instance. If the number or order of hook calls changes between renders, React loses this association and produces subtle, hard-to-diagnose bugs. The naming convention of prefixing custom hooks with the word use is not purely stylistic. The React ESLint plugin uses this prefix to identify custom hooks and apply rule checking inside them. A function named getUser that calls useState internally will silently skip the linter checks, removing the safety net that prevents conditional hook usage.

Building a useFetch Hook for Data Fetching

A data-fetching hook is among the most commonly needed custom hooks in any React application. The useFetch hook accepts a URL string and returns an object containing three values: the fetched data, a boolean loading flag, and any error that occurred. Inside the hook, useState tracks all three values and useEffect triggers the fetch whenever the URL prop changes. The most important detail is the AbortController that cancels the in-flight request if the component unmounts before the response arrives. In the useEffect cleanup function, calling controller.abort prevents the infamous state update on unmounted component warning and eliminates memory leaks. Components that consume this hook stay thin and focused entirely on rendering. You can extend the hook later to add retry logic, request caching, stale-while-revalidate behavior, or request deduplication without touching any of the components that use it.

Building a useLocalStorage Hook for Persistent State

A useLocalStorage hook gives you the exact same interface as useState while automatically persisting the value to the browser localStorage on every update. The hook initializes state by reading from localStorage using the provided key, falling back to the supplied default value when no stored value exists. It uses JSON.stringify for writing complex values and JSON.parse when reading them back, enabling you to store objects, arrays, booleans, and numbers rather than only plain strings. Always wrap the localStorage read inside a try-catch block because JSON.parse throws a SyntaxError on malformed stored data, and certain browsers block storage access completely in private browsing mode. The hook returns a tuple identical to useState, making it a drop-in replacement wherever you need persistence. This pattern is ideal for user preferences such as theme selection, collapsed sidebar state, and partially completed form data that should survive page refreshes.

Building a useDebounce Hook to Throttle API Calls

Debouncing delays the execution of a function until after a specified time has elapsed since it was last invoked. This technique is essential for search inputs where firing an API call on every keystroke would produce dozens of unnecessary network requests and poor user experience. The useDebounce hook accepts a reactive value and a delay in milliseconds, and returns a debounced copy of that value that only updates after the delay period has passed without a new value arriving. Inside the hook, useEffect sets a setTimeout to update the debounced value after the specified delay, while the cleanup function clears the existing timeout whenever the input value changes, effectively resetting the countdown. Components feed their live input state into the hook and use only the returned debounced value as the dependency for any useEffect that makes network requests. This keeps the debounce logic isolated and reusable across every search input in the application.

Composing Custom Hooks Together

One of the most valuable properties of custom hooks is that they compose naturally and transparently. You can call other custom hooks inside your custom hooks, building up sophisticated behavior from simple, independently tested building blocks. A useSearchResults hook could call your useDebounce hook to delay the search term, then pass the debounced value to your useFetch hook to retrieve matching records from an API. Neither primitive hook needs to know the other exists. This compositional model scales dramatically better than the older higher-order component pattern, where combining multiple behaviors required wrapping components in multiple layers of wrapper functions that obscured the component tree. When requirements change in one primitive hook, you update it in one place and every composed hook built on top of it benefits automatically. This encourages building a library of small, focused, well-tested hooks that can be mixed and matched across the entire codebase.

When to Extract Logic into a Custom Hook

The decision to create a custom hook should be driven by genuine need for reuse or a meaningful improvement in code clarity, not by a desire to reduce the line count of a component file. When identical stateful logic appears in two or more separate components, a custom hook is the right abstraction because it eliminates duplication and creates a single source of truth for that behavior. When a component has grown so complex that its inline hook usage is difficult to follow, grouping related hooks into a named custom hook significantly improves readability even if the hook is only used in that one component. On the other hand, extracting a hook from a component that has simple, single-use logic adds indirection without any benefit. The same abstraction principle applies here as everywhere in software: wait until duplication is real and painful before extracting it. Premature extraction creates complexity that slows development more than it helps.

Share this article

All posts
#React#Hooks#JavaScript#Component
Abdur Razzak — Full Stack Web Developer
⭐ Top Rated

Upwork Top Rated Developer

Work With a Developer Clients Trust