Best React Hook Libraries You Should Know in 2026
TL;DR
usehooks-ts for TypeScript-first hooks; react-use for the complete collection; ahooks for enterprise React apps. usehooks-ts (~3M weekly downloads) is the TypeScript-native collection — smaller, well-typed, tree-shakeable. react-use (~8M downloads) is the massive collection (100+ hooks) but has a mix of quality. ahooks (~2M) from Alibaba is comprehensive and well-maintained for enterprise use cases. For most projects, usehooks-ts covers the essentials with the best TypeScript support.
Key Takeaways
- react-use: ~8M weekly downloads — 100+ hooks, comprehensive, some outdated
- usehooks-ts: ~3M downloads — TypeScript-first, tree-shakeable, actively maintained
- ahooks: ~2M downloads — Alibaba, enterprise patterns, request management
- TanStack Query — best async/data fetching hook (not just a utility library)
- 2026 trend — write custom hooks over installing large libraries for simple use cases
Why Hook Libraries Matter
React hooks have fundamentally changed how state, effects, and browser API interactions are written. But many of the browser APIs you need to wrap — localStorage, IntersectionObserver, ResizeObserver, matchMedia, addEventListener — require careful handling to avoid memory leaks, stale closures, and SSR hydration mismatches.
Hook libraries solve this problem by providing tested, type-safe implementations of common patterns. Instead of writing a useLocalStorage hook that handles SSR correctly, serializes JSON, and syncs across tabs, you install a package that has already solved these edge cases.
The question in 2026 is less "should I use a hook library?" and more "which one fits my use case?" The three main contenders — usehooks-ts, react-use, and ahooks — each take a different approach to scope, TypeScript quality, and target audience.
Every React application of meaningful size reaches a point where the same patterns appear repeatedly: debouncing input events, reading from localStorage with a fallback, toggling boolean state, detecting whether a component is still mounted before calling setState. Solving each of these from scratch in every project is a waste of time and introduces subtle bugs. Hook libraries represent the collective solutions to these problems, battle-tested across thousands of production applications.
The three libraries covered here are not interchangeable. They represent different philosophical positions. usehooks-ts is a curated set of high-quality TypeScript hooks — quality over quantity. react-use is the comprehensive archive — breadth over depth. ahooks is the enterprise toolkit — power and patterns over simplicity. Understanding these differences helps you pick the right tool for your project or, in many cases, install two of them for different purposes.
usehooks-ts: TypeScript-First Precision
usehooks-ts (~3M weekly downloads) is the cleanest choice for TypeScript projects. Every hook is written in TypeScript with precise types — no any, no loosely typed return values. The library is tree-shakeable, meaning bundlers can eliminate hooks you don't import. And it's actively maintained with a small, curated set of hooks rather than trying to be exhaustive.
The philosophy is quality over quantity. Where react-use has 100+ hooks of varying quality, usehooks-ts ships about 35 hooks that all meet a high bar for TypeScript quality and behavioral correctness. The documentation for each hook includes edge cases, options, and TypeScript usage examples.
npm install usehooks-ts
// usehooks-ts — essential hooks with great TypeScript support
import {
useLocalStorage,
useDebounce,
useEventListener,
useOnClickOutside,
useWindowSize,
useMediaQuery,
useToggle,
useCounter,
useCopyToClipboard,
useIntersectionObserver,
} from 'usehooks-ts';
useLocalStorage is one of the most useful hooks in the library. It provides a useState-like API backed by localStorage, handles JSON serialization automatically, works correctly on the server (SSR-safe with a configurable initial value), and is fully typed:
// useLocalStorage — persisted state with TypeScript type inference
import { useLocalStorage } from 'usehooks-ts';
interface UserPreferences {
theme: 'light' | 'dark';
language: string;
notificationsEnabled: boolean;
}
function Settings() {
const [preferences, setPreferences] = useLocalStorage<UserPreferences>(
'user-preferences',
{ theme: 'light', language: 'en', notificationsEnabled: true }
);
// setPreferences accepts a partial updater function, like useState:
const toggleTheme = () =>
setPreferences(prev => ({
...prev,
theme: prev.theme === 'light' ? 'dark' : 'light',
}));
return (
<div>
<button onClick={toggleTheme}>
Switch to {preferences.theme === 'light' ? 'dark' : 'light'} mode
</button>
</div>
);
}
useDebounce is clean and predictable. It returns a debounced value that updates only after the input has stopped changing for the specified delay:
// useDebounce — debounce search input to reduce API calls
import { useDebounce } from 'usehooks-ts';
import { useEffect, useState } from 'react';
function SearchBar({ onSearch }: { onSearch: (q: string) => void }) {
const [inputValue, setInputValue] = useState('');
const debouncedValue = useDebounce(inputValue, 300); // 300ms delay
useEffect(() => {
if (debouncedValue.length > 0) {
onSearch(debouncedValue); // Only fires after typing stops
}
}, [debouncedValue, onSearch]);
return (
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Search packages..."
/>
);
}
useOnClickOutside solves the classic "close dropdown when clicking outside" pattern correctly. It handles multiple refs and cleans up event listeners on unmount:
// useOnClickOutside — close dropdowns, modals, tooltips
import { useRef, useState } from 'react';
import { useOnClickOutside } from 'usehooks-ts';
function Dropdown() {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
// Closes when clicking anywhere outside the ref'd element:
useOnClickOutside(ref, () => setIsOpen(false));
return (
<div ref={ref} style={{ position: 'relative' }}>
<button onClick={() => setIsOpen(!isOpen)}>
Options {isOpen ? '▲' : '▼'}
</button>
{isOpen && (
<div className="dropdown-menu">
<button>Edit</button>
<button>Delete</button>
</div>
)}
</div>
);
}
useWindowSize provides reactive viewport dimensions for responsive layouts that can't be achieved with CSS alone. useMediaQuery gives you CSS media query matching in JavaScript — useful for conditionally rendering components based on viewport, reduced motion, or color scheme preferences:
// useWindowSize + useMediaQuery — responsive layout decisions in JS
import { useWindowSize, useMediaQuery } from 'usehooks-ts';
function ResponsiveLayout() {
const { width, height } = useWindowSize();
const isMobile = useMediaQuery('(max-width: 768px)');
const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
return (
<div>
<p>Viewport: {width} x {height}</p>
{isMobile ? <MobileNav /> : <DesktopNav />}
{!prefersReducedMotion && <AnimatedHero />}
</div>
);
}
Other standout hooks: useIntersectionObserver (lazy loading, infinite scroll triggers), useToggle (boolean flip with stable callback reference), and useCopyToClipboard (clipboard API with success feedback).
The library's deliberate restraint is a feature. When you install usehooks-ts, you get ~35 hooks you can trust rather than 100+ of unknown quality. For TypeScript projects, this is usually the right starting point.
react-use: The Comprehensive Collection
react-use (~8M weekly downloads) is the oldest and most downloaded hook library. It takes a maximalist approach — 100+ hooks covering virtually every browser API, sensor, animation timing, and UI state pattern you might need. The breadth is genuinely impressive.
The caveats are also real. Not all hooks are equally maintained. Some were written before React's strict mode and have minor issues. TypeScript types are good but not as precise as usehooks-ts. And for tree-shaking to work you need to import from the specific entry point rather than the barrel (import { usePrevious } from 'react-use/lib/usePrevious').
That said, react-use has hooks that simply don't exist elsewhere and work perfectly well in practice:
// react-use — highlights from 100+ hooks
import {
usePrevious, // Previous render's value
useIdle, // User inactivity detection
useTitle, // Document title management
useFullscreen, // Fullscreen API wrapper
useLongPress, // Long press gesture
useMouseHovered, // Mouse position relative to element
useSpeech, // Web Speech API (text to speech)
useGeolocation, // Geolocation API
useNetworkState, // Network connectivity status
} from 'react-use';
useDebounce from react-use follows the same value-debouncing pattern as usehooks-ts. useToggle is a common simple pattern — it wraps boolean state with a stable toggle function, avoiding the need to write setState(prev => !prev) everywhere:
// useToggle — react-use's simple boolean flip
import { useToggle } from 'react-use';
function ThemeToggler() {
const [isDark, toggleDark] = useToggle(false);
return (
<button onClick={toggleDark}>
{isDark ? 'Switch to Light' : 'Switch to Dark'}
</button>
);
}
usePrevious is one of the most useful hooks in the library — and something that never made it into usehooks-ts. It captures the value from the previous render, which is useful for animations, change detection, and displaying before/after comparisons:
// usePrevious — track previous state value for change detection
import { usePrevious } from 'react-use';
function AnimatedCounter({ count }: { count: number }) {
const prevCount = usePrevious(count);
const direction = count > (prevCount ?? count) ? 'up' : 'down';
return (
<div className={`counter count-${direction}`}>
{count}
</div>
);
}
useIdle detects user inactivity, which is useful for session timeout warnings, auto-saving, or reducing background polling. useNetworkState is invaluable for PWAs and offline-capable applications:
// useNetworkState — show offline banner for PWA
import { useNetworkState } from 'react-use';
function OfflineBanner() {
const { online } = useNetworkState();
if (online) return null;
return (
<div className="banner banner-warning">
You are offline. Changes will sync when reconnected.
</div>
);
}
The practical advice: use react-use for the hooks it uniquely provides (usePrevious, useIdle, useGeolocation, useSpeech), and prefer usehooks-ts for the common hooks where TypeScript quality matters most. You can install both — they don't conflict, and the combined bundle is still smaller than most UI component libraries.
ahooks: Enterprise React Patterns
ahooks (~2M weekly downloads) is maintained by the Alibaba/Ant Design ecosystem. It targets enterprise React applications where data fetching patterns, complex state, and performance at scale matter more than bundle size. The library ships about 70 hooks organized into categories: state, effect, DOM, advanced, and scene (pre-built higher-level patterns).
The star feature is useRequest — an async state management hook that handles loading, error, and data states while also supporting polling, caching, debouncing, throttling, parallel requests, and more:
npm install ahooks
// ahooks useRequest — async state management with superpowers
import { useRequest } from 'ahooks';
async function fetchUser(id: number) {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error('Failed to fetch user');
return res.json() as Promise<{ id: number; name: string; email: string }>;
}
function UserProfile({ userId }: { userId: number }) {
const { data, loading, error, refresh } = useRequest(
() => fetchUser(userId),
{
refreshDeps: [userId], // Re-fetch when userId changes
pollingInterval: 30_000, // Auto-refresh every 30 seconds
cacheKey: `user-${userId}`, // Cache response in memory
staleTime: 60_000, // Consider cache valid for 60 seconds
retryCount: 3, // Retry on failure
}
);
if (loading) return <div>Loading...</div>;
if (error) return <button onClick={refresh}>Retry</button>;
return (
<div>
<h2>{data?.name}</h2>
<p>{data?.email}</p>
<button onClick={refresh}>Refresh</button>
</div>
);
}
useDebounceFn and useThrottleFn wrap functions rather than values, which is often more ergonomic than debouncing a state value:
import { useDebounceFn, useThrottleFn } from 'ahooks';
function SearchInput() {
const { run: debouncedSearch } = useDebounceFn(
(value: string) => {
fetch(`/api/search?q=${value}`);
},
{ wait: 300 }
);
return (
<input
onChange={(e) => debouncedSearch(e.target.value)}
placeholder="Search..."
/>
);
}
useVirtualList provides virtualized rendering for large lists without external dependencies like react-window:
import { useVirtualList } from 'ahooks';
import { useRef } from 'react';
function VirtualizedList({ items }: { items: string[] }) {
const containerRef = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
const [list] = useVirtualList(items, {
containerTarget: containerRef,
wrapperTarget: wrapperRef,
itemHeight: 52,
overscan: 5,
});
return (
<div ref={containerRef} style={{ height: 400, overflow: 'auto' }}>
<div ref={wrapperRef}>
{list.map(({ data, index }) => (
<div key={index} style={{ height: 52 }}>
{data}
</div>
))}
</div>
</div>
);
}
ahooks is particularly well-suited to Ant Design-heavy enterprise UIs, where useAntdTable integrates directly with Ant Design's Table component to handle pagination, sorting, and filtering state automatically. For teams already in the Alibaba ecosystem, ahooks offers seamless integration that would take significant custom code to replicate.
The useRequest hook deserves special attention because it covers a real gap. Many teams use TanStack Query for server state management, and rightly so. But for simpler applications that don't warrant a full server state library, useRequest provides 80% of the functionality — polling, retries, caching, loading states — with a much simpler setup. For a dashboard that needs to refresh data every 30 seconds and retry on failure, useRequest is often the right level of abstraction.
Writing Custom Hooks: When to Skip the Library
An important counterpoint: for simple cases, the best hook is one you write yourself. Hook libraries solve hard problems — cross-browser quirks, SSR hydration, cleanup on unmount — but for domain-specific logic, a custom hook is often cleaner and more maintainable than bending a library hook to your use case.
There are categories of hooks that should almost always be custom in your codebase:
- Business domain hooks —
useCart,useCurrentUser,usePermissions,useCheckout. These depend on your API shape and data model. - Feature-specific state —
useVideoPlayer,useMapInteractions,useDragToReorder. Libraries don't know your data structure. - Wrapper hooks — thin wrappers around your own services, e.g.,
useAnalyticsthat calls your specific tracking library.
Here's what a simple useDebounce implementation looks like to demystify what libraries provide:
// Custom useDebounce — understand what the library abstracts
import { useState, useEffect } from 'react';
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Cleanup: cancel timer if value or delay changes before it fires
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
That's 15 lines. The library version handles additional edge cases — what happens if the component unmounts before the timeout fires, what happens with concurrent mode and strict mode double-mounting — but the core logic is this simple. Understanding the implementation makes you a better consumer of the library.
The broader custom hook that's worth writing yourself is a useFetch for application-specific fetching:
// Custom useFetch — baseline for understanding what libraries add
import { useState, useEffect, useRef } from 'react';
interface FetchState<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
function useFetch<T>(url: string): FetchState<T> {
const [state, setState] = useState<FetchState<T>>({
data: null,
loading: true,
error: null,
});
const abortRef = useRef<AbortController>();
useEffect(() => {
abortRef.current?.abort();
abortRef.current = new AbortController();
setState({ data: null, loading: true, error: null });
fetch(url, { signal: abortRef.current.signal })
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json() as Promise<T>;
})
.then(data => setState({ data, loading: false, error: null }))
.catch(error => {
if (error.name !== 'AbortError') {
setState({ data: null, loading: false, error });
}
});
return () => abortRef.current?.abort();
}, [url]);
return state;
}
This 40-line hook handles the most common pitfalls: canceling in-flight requests when the URL changes, setting loading state, and propagating errors. But it doesn't handle caching, polling, retries, or stale-while-revalidate semantics. That's what useRequest from ahooks or TanStack Query provides on top of this baseline. Knowing what's under the hood helps you decide when a library hook is worth the dependency and when your custom implementation is sufficient.
The guideline: write custom hooks when the logic is specific to your domain or when a library hook's API doesn't match your mental model. Use library hooks for standard browser API wrappers where you'd otherwise spend time debugging edge cases.
Package Health Comparison
| Library | Weekly Downloads | TypeScript Quality | Bundle Size | Notable Hooks | Maintenance |
|---|---|---|---|---|---|
react-use | ~8M | Good | ~60KB | usePrevious, useIdle, useGeolocation | Active (some hooks stale) |
usehooks-ts | ~3M | Excellent | ~15KB (tree-shakeable) | useLocalStorage, useDebounce, useOnClickOutside | Active, high quality |
ahooks | ~2M | Very Good | ~80KB | useRequest, useVirtualList, useLockFn | Active (Alibaba backed) |
When to Choose
Standard TypeScript React project — usehooks-ts
For the majority of React applications, usehooks-ts is the right default. The hooks you'll use most — useLocalStorage, useDebounce, useOnClickOutside, useWindowSize, useMediaQuery — are all present, precisely typed, and well-documented. The small footprint and tree-shakeability make it bundle-friendly. If you're starting a new project and want one utility hook library, this is it.
Need a specific hook that usehooks-ts doesn't have — react-use
react-use's breadth means it has hooks that simply don't exist elsewhere. If you need usePrevious, useIdle, useGeolocation, useLongPress, useNetworkState, or useSpeech, react-use is the go-to. Install it alongside usehooks-ts — they don't conflict. Import from the specific module path for tree-shaking: import { usePrevious } from 'react-use/lib/usePrevious'.
Enterprise app with complex data fetching — ahooks
If you're building a dashboard-heavy application, data-intensive admin UI, or any app where useRequest's polling, caching, and retry semantics would save significant boilerplate, ahooks is worth its weight. The useVirtualList hook alone justifies installation for apps with large list rendering requirements. If your team is already using Ant Design components, ahooks is a natural companion.
Async data at scale — TanStack Query instead
For serious server state management — caching, background refetching, optimistic updates, infinite queries — none of the above hook libraries match TanStack Query (formerly React Query). It's not in the same category (it's a full server state library, not a utility hooks collection), but it's worth mentioning because useRequest from ahooks and useAsync from react-use often get replaced by TanStack Query in production codebases as applications grow.
Simple domain logic — write your own
For hooks that encode your business logic (useCart, useCurrentUser, usePermissions), resist the urge to find a library. These hooks are better custom: they match your API shape exactly, they don't carry library overhead, and they're easier for new team members to understand.
See the live comparison
View react use vs. usehooks on PkgPulse →