@gmana/react-hooks
v0.0.17
Published
React hooks
Readme
@gmana/react-hooks
A comprehensive collection of production-ready React hooks for modern applications. Built with TypeScript and fully tested.
✨ Features
- 🚀 80+ Production-Ready Hooks - Covering state management, UI interactions, network requests, and more
- 📦 TypeScript Support - Full type safety with comprehensive TypeScript definitions
- 🧪 Thoroughly Tested - Complete test coverage with Vitest
- 📱 Mobile-First - Responsive design helpers and mobile-optimized hooks
- 🎯 Tree-Shakable - Import only what you need
- ⚡ Next.js Ready - Full SSR support and hydration safety for Next.js applications
- 🔄 Server-Side Rendering - SSR compatible with Next.js and other frameworks
- 📖 Well Documented - Comprehensive examples and API documentation
📦 Installation
# npm
npm install @gmana/react-hooks
# yarn
yarn add @gmana/react-hooks
# pnpm
pnpm add @gmana/react-hooks
# bun
bun add @gmana/react-hooks🚀 Quick Start
import React from "react"
import { useBoolean, useCounter, useFetch } from "@gmana/react-hooks"
function App() {
const { value: isVisible, toggle } = useBoolean(false)
const { count, increment, decrement } = useCounter(0)
const { data, error } = useFetch<{ title: string }>("https://api.example.com/data")
if (error) return <div>Error loading data</div>
if (!data) return <div>Loading...</div>
return (
<div>
<h1>{data.title}</h1>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={toggle}>{isVisible ? "Hide" : "Show"} Content</button>
{isVisible && <div>Toggle content</div>}
</div>
)
}📚 Available Hooks
🔄 State Management
useBoolean— Boolean state withsetTrue/setFalse/toggleuseToggle— Boolean state as a tuple, where the setter doubles as a toggleuseToggleState— Boolean state destructurable as either an array or an objectuseCounter— Numeric counter withmin/maxclamping and a custom stepuseArray— Array state with immutablepush/insertAt/update/removehelpersuseMap—Mapstate with immutable actions and no-op bail-outsuseSet—Setstate for selection, filters, and expansion stateuseQueue— FIFO queue for toasts, tours, and retry backlogsuseObjectState— Object state updated by shallow merge instead of spreadinguseStateHistory— State with undo / redo and a capped historyuseControllableState— One hook for both controlled and uncontrolled componentsuseStep— 1-based step index for wizards, clamped rather than throwinguseRafState— State whose updates are coalesced to one per animation frame
🎨 UI & Layout
useResizeObserver— Measure an element viaResizeObserveruseElementSize— Measure an element through a callback ref, nouseRefplumbinguseDimensions— Border-box dimensions of a referenced elementuseWindowSize— Viewport dimensions, with optional resize debouncinguseWindowScroll— Scroll position, direction, and scroll helpersuseScroll— Whether the page is scrolled past a thresholduseHover— Whether the pointer is over an elementuseInView— Whether an element is on screenuseIntersectionObserver— FullIntersectionObserveraccess, withfreezeOnceVisibleuseInfiniteScroll— Load the next page when a sentinel scrolls into viewuseOnClickOutside— Detect clicks outside one element or severaluseClickAnyWhere— Detect any click in the documentuseLockedBody— Lock body scroll, with scrollbar compensation and nested locksuseFocusTrap— Confine keyboard focus to a container (modals, drawers)useFullscreen— Present an element fullscreenuseSidebar— Open/closed state shaped for Radix-style componentsuseImageOnLoad— Cross-fade a blurred thumbnail into a full-size imageuseTextSelection— The current selection and its bounding rectuseMutationObserver— React to DOM changes made outside React
🌐 Network & Data
useFetch— JSON requests with caching, abort,skip, andrefetchuseAsync— Run any async function on demand and track its lifecycleuseAsyncEffect— Async effects with cancellation and anAbortSignaluseNetworkInformation— Connection quality and online statususeOfflineDetector/useOnlineStatus— Online/offline statususeBroadcastChannel— Send messages between tabs on the same origin
💾 Storage & Persistence
useLocalStorage—localStoragestate, synced across hooks and tabsuseSessionStorage— The same, scoped to the tabuseReadLocalStorage— Read a key you don't own, staying in syncuseCookie— Read and write a cookie as statesetCookie/getCookie/removeCookie— Standalone cookie helpersuseEstimateStorage— Storage usage and remaining quota
📱 Device & Browser APIs
useMediaQuery— CSS media query matchinguseIsMobile— Whether the viewport is below a mobile breakpointuseScreen— Screen metrics, updated on resize and rotationuseOrientation— Portrait/landscape and rotation angleuseGeolocation— Device position, withwatchand gesture-friendlyrequestusePermission— Observe a browser permission without promptinguseCopyToClipboard— Copy text, with a transient "copied!" stateuseClipboard— Deprecated alias foruseCopyToClipboarduseScript— Load an external script and track its statususeDocumentTitle— Setdocument.titlewhile mounteduseFavicon— Swap the page favicon while mounteduseDocumentVisibility/useIsDocumentVisible— Whether the tab is visibleusePreferredLanguage/usePreferredLanguages— The user's language preferencesusePrefersReducedMotion— Whether the user asked to reduce motion
⌨️ Input & Interaction
useKeyPress— Keyboard shortcuts, including cross-platform"mod+k"useLongPress— Press-and-hold gestures across mouse, touch, and penuseIdle— Whether the user has been inactiveusePageLeave— Exit-intent detectionuseUnloadWarning— Warn before leaving with unsaved changes
⏱️ Timing & Effects
useDebounce— Debounce a value, withflush/cancel/maxWaituseDebouncedCallback— Debounce a callback, with a stable identityuseThrottle— Throttle a callback, with leading/trailing controluseTimeout— DeclarativesetTimeoutwith manual controlsuseInterval— DeclarativesetIntervalwith a pausable delayuseCountdown— Drift-free countdown with pause and resume
🔧 Utilities
useEventCallback— A stable callback that always calls the latest functionuseLatest— A ref that always holds the newest valueusePrevious— The value from the previous renderuseMergedRef— Combine several refs into oneuseEventListener— Typed event subscription for window, document, elements, and media queriesuseMount/useUnmount— Run a callback once on mount or unmountuseEffectOnce— Run an effect once (not StrictMode-guarded)useUpdateEffect— Run an effect on updates but not on mountuseIsFirstRender— Whether this is the first renderuseIsMounted— Whether the component is still mounted
🎨 Theme & Appearance
useDarkMode— Dark mode toggle persisted tolocalStorageuseTernaryDarkMode— Light / dark / system theme management
🔧 Next.js & SSR
useHydrated— Whether React has hydrated — gate client-only UI on thisuseIsClient— Whether a DOM is availableuseSsr—isBrowser/isServer/isHydratedtogetheruseIsomorphicLayoutEffect—useLayoutEffectin the browser,useEffecton the server
💡 Usage Examples
State Management
import { useBoolean, useCounter, useArray } from "@gmana/react-hooks"
function StateExample() {
// Boolean state with helpers
const { value: isOpen, setTrue: open, setFalse: close, toggle } = useBoolean(false)
// Counter with operations
const { count, increment, decrement, reset } = useCounter(0)
// Array management
const { array, push, remove, filter, clear } = useArray([1, 2, 3])
return (
<div>
<button onClick={toggle}>{isOpen ? "Close" : "Open"}</button>
<button onClick={increment}>Count: {count}</button>
<div>Array: {array.join(", ")}</div>
<button onClick={() => push(Math.random())}>Add Random</button>
</div>
)
}UI Interactions
import { useHover, useOnClickOutside, useElementSize } from "@gmana/react-hooks"
function UIExample() {
const [setRef, { width, height }] = useElementSize()
const hoverRef = useRef(null)
const isHovered = useHover(hoverRef)
const dropdownRef = useRef(null)
const [isDropdownOpen, setIsDropdownOpen] = useState(false)
useOnClickOutside(dropdownRef, () => setIsDropdownOpen(false))
return (
<div ref={setRef}>
<p>
Element size: {width}x{height}
</p>
<div ref={hoverRef} style={{ background: isHovered ? "blue" : "gray" }}>
Hover me!
</div>
<div ref={dropdownRef}>
<button onClick={() => setIsDropdownOpen(!isDropdownOpen)}>Toggle Dropdown</button>
{isDropdownOpen && <div>Dropdown content</div>}
</div>
</div>
)
}Data Fetching
import { useFetch, useDebounce } from "@gmana/react-hooks"
function DataExample() {
const [query, setQuery] = useState("")
const debouncedQuery = useDebounce(query, 500)
const { data, error } = useFetch(debouncedQuery ? `https://api.example.com/search?q=${debouncedQuery}` : null)
return (
<div>
<input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search..." />
{error && <p>Error: {error.message}</p>}
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
)
}Theme Management
import { useDarkMode, useTernaryDarkMode } from "@gmana/react-hooks"
function ThemeExample() {
// Simple dark mode
const { isDarkMode, toggle, enable, disable } = useDarkMode()
// Advanced theme with system preference
const { isDarkMode: isAdvancedDark, ternaryDarkMode, setTernaryDarkMode } = useTernaryDarkMode()
return (
<div className={isDarkMode ? "dark" : "light"}>
<button onClick={toggle}>{isDarkMode ? "Light" : "Dark"} Mode</button>
<select value={ternaryDarkMode} onChange={(e) => setTernaryDarkMode(e.target.value)}>
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="system">System</option>
</select>
</div>
)
}Next.js Integration
import { useHydrated, useMediaQuery } from "@gmana/react-hooks"
function NextJSExample() {
const isHydrated = useHydrated()
const isMobile = useMediaQuery("(max-width: 768px)")
// Prevent hydration mismatches
if (!isHydrated) {
return <div>Loading...</div>
}
return (
<div>
<h1>My Next.js App</h1>
{isMobile ? <MobileLayout /> : <DesktopLayout />}
</div>
)
}⚠️ Migrating
Most of this release is additive — new hooks, new options, and bug fixes that need no code changes. The following are the deliberate breaking changes.
useEstimateStorage returns an object
It used to return the remaining megabytes as a bare number, which discarded the usage and quota figures the browser already provided.
// Before
const remainingMB = useEstimateStorage()
// After
const { estimate, refresh } = useEstimateStorage()
const remainingMB = estimate?.remainingMB ?? 0useScreen returns a plain object, not Screen
Screen's properties live on its prototype, so the live object never compared unequal
and React skipped the re-render. The values are now copied into a plain ScreenInfo.
Reading screen?.width / screen?.height is unchanged; reading exotic Screen
members is not supported.
useThrottle's third argument is now options
The old deps array is no longer needed — the callback is kept in a ref — and is
still accepted and ignored, so existing call sites keep working. New code should pass
{ leading, trailing } instead. The returned function now also carries flush and
cancel.
useCopyToClipboard resolves to a boolean
copyToClipboard now returns Promise<boolean> rather than void, and the hook
additionally returns copiedValue and reset. Existing calls that ignore the return
value are unaffected.
useIsMobile takes an options object
useIsMobile() still works. A custom breakpoint is now useIsMobile({ breakpoint: 1024 }).
useIntersection is now useInView
useIntersection remains as a deprecated alias with the same signature.
Hydration behaviour changed (intentionally)
useLocalStorage, useReadLocalStorage, useMediaQuery, useIsMobile,
useWindowSize, and useScreen now report their fallback value during the first
client render and the real value immediately after hydration. This is what makes them
hydration-safe. If your app is client-only and you want the old
read-immediately behaviour, opt in explicitly:
const [value] = useLocalStorage('key', 'default', { initializeWithValue: true })
const isDesktop = useMediaQuery('(min-width: 768px)', { initializeWithValue: true })
const size = useWindowSize({ initializeWithValue: true })useSsr().isHydrated now means what it says
It previously returned true on the very first client render — the exact moment the
markup must still match the server's — so using it as documented caused the mismatch
it claimed to prevent. It now flips to true only after hydration.
🛠️ Development
Prerequisites
- Node.js ≥ 18
- Bun ≥ 1.0.0 (recommended) or npm/yarn
Setup
# Clone the repository
git clone https://github.com/sun-sreng/npm-gmana-react-hooks.git
cd npm-gmana-react-hooks
# Install dependencies
bun install
# Start development mode
bun run devScripts
bun run dev- Start development mode with watchbun run build- Build the librarybun run test- Run testsbun run test:watch- Run tests in watch modebun run lint- Lint codebun run format- Format code with oxfmtbun run typecheck- Type-checksrc/bun run check- Run lint, format check, and typecheck
Testing
We use Vitest for testing with React Testing Library:
# Run all tests
bun run test
# Run tests in watch mode
bun run test:watch
# Run specific test file
bun run test src/lib/use-boolean.test.tsBuilding
The library is built with tsdown (Rolldown) and compiled through the React Compiler:
bun run buildThis generates:
dist/index.js- ES module bundledist/index.d.ts- TypeScript declarations
📋 Requirements
Peer Dependencies
- React ≥ 18.0.0 (
useSyncExternalStore) - React DOM ≥ 18.0.0
- TypeScript ≥ 5.9.3 (for TypeScript projects)
Browser Support
- Modern browsers with ES2022 support
- React Native (most hooks)
- Next.js 13+ with App Router and Pages Router
- Server-Side Rendering environments
🤝 Contributing
We welcome contributions! Please see our Contributing Guidelines for details.
Quick Contribution Guide
- Fork the repository
- Create a feature branch:
git checkout -b feature/new-hook - Add your hook with tests and documentation
- Run tests:
bun run test - Submit a pull request
Adding a New Hook
- Create your hook in
src/lib/use-your-hook.ts - Add comprehensive tests in
src/lib/use-your-hook.test.ts - Export the hook in
src/index.ts - Update this README with documentation
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- React team for the amazing hooks API
- All contributors who helped build this library
- The open-source community for inspiration and feedback
📖 Documentation
- 📚 Next.js Integration Guide - Complete guide for using with Next.js
- 🔧 Development Guide - Information for contributors and maintainers
📞 Support
Made with ❤️ by Sun Sreng
