πͺ A collection of production-ready React hooks for modern applications
Installation β’ Packages β’ Quick Start β’ Features
β οΈ Pre-release Notice: This project is currently in version0.x.x(alpha/beta stage). APIs may change between minor versions. While fully functional and tested, please use with caution in production environments.π§ Actively Developing: New hooks are being added regularly. Stay tuned for more utilities!
usefy is a collection of production-ready custom hooks designed for modern React applications. All hooks are written in TypeScript, providing complete type safety, comprehensive testing, and minimal bundle size.
- π Zero Dependencies β Pure React implementation with no external dependencies
- π¦ Tree Shakeable β Import only the hooks you need to optimize bundle size
- π· TypeScript First β Complete type safety with full autocomplete support
- β‘ SSR Compatible β Works seamlessly with Next.js, Remix, and other SSR frameworks
- π§ͺ Well Tested β High test coverage ensures reliability and stability
- π Well Documented β Detailed documentation with practical examples
Install all hooks at once:
# npm
npm install @usefy/usefy
# yarn
yarn add @usefy/usefy
# pnpm
pnpm add @usefy/usefyYou can also install only the hooks you need:
# Example: Install only use-toggle
pnpm add @usefy/use-toggle
# Install multiple packages
pnpm add @usefy/use-debounce @usefy/use-local-storageAll packages require React 18 or 19:
{
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
}
}| Hook | Description | npm | Coverage |
|---|---|---|---|
| @usefy/use-toggle | Boolean state management with toggle, setTrue, setFalse | ||
| @usefy/use-counter | Counter state with increment, decrement, reset | ||
| @usefy/use-debounce | Value debouncing with leading/trailing edge | ||
| @usefy/use-debounce-callback | Debounced callbacks with cancel/flush/pending | ||
| @usefy/use-throttle | Value throttling for rate-limiting updates | ||
| @usefy/use-throttle-callback | Throttled callbacks with cancel/flush/pending | ||
| @usefy/use-local-storage | localStorage persistence with cross-tab sync | ||
| @usefy/use-session-storage | sessionStorage persistence for tab lifetime | ||
| @usefy/use-click-any-where | Document-wide click event detection | ||
| @usefy/use-copy-to-clipboard | Clipboard copy with fallback support | ||
| @usefy/use-event-listener | DOM event listener with auto cleanup | ||
| @usefy/use-on-click-outside | Outside click detection for modals/dropdowns | ||
| @usefy/use-timer | Countdown timer with drift compensation and formats |
import {
useToggle,
useCounter,
useDebounce,
useLocalStorage,
useCopyToClipboard,
useEventListener,
useOnClickOutside,
} from "@usefy/usefy";
function App() {
// Boolean state management
const { value: isOpen, toggle, setFalse: close } = useToggle(false);
// Counter with controls
const { count, increment, decrement, reset } = useCounter(0);
// Debounced search
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 300);
// Persistent theme preference
const [theme, setTheme] = useLocalStorage("theme", "light");
// Copy functionality
const [copiedText, copy] = useCopyToClipboard();
return (
<div data-theme={theme}>
{/* Modal */}
<button onClick={toggle}>Open Modal</button>
{isOpen && (
<div className="modal">
<button onClick={close}>Close</button>
</div>
)}
{/* Counter */}
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
{/* Search */}
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
{/* Theme Toggle */}
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Toggle Theme
</button>
{/* Copy */}
<button onClick={() => copy("Hello World!")}>
{copiedText ? "Copied!" : "Copy"}
</button>
</div>
);
}import { useToggle } from "@usefy/use-toggle";
import { useDebounce } from "@usefy/use-debounce";
function SearchModal() {
const { value: isOpen, toggle } = useToggle(false);
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (debouncedQuery) {
searchAPI(debouncedQuery);
}
}, [debouncedQuery]);
return (
<>
<button onClick={toggle}>Search</button>
{isOpen && (
<input value={query} onChange={(e) => setQuery(e.target.value)} />
)}
</>
);
}useToggle β Boolean state with utility functions
const { value, toggle, setTrue, setFalse, setValue } = useToggle(false);Perfect for modals, dropdowns, accordions, and switches.
useCounter β Counter state with controls
const { count, increment, decrement, reset } = useCounter(0);Ideal for quantity selectors, pagination, and score tracking.
useDebounce β Debounce value updates
const debouncedValue = useDebounce(value, 300, {
leading: false,
trailing: true,
maxWait: 1000,
});Best for search inputs, form validation, and API calls.
useDebounceCallback β Debounce function calls
const debouncedFn = useDebounceCallback(callback, 300);
debouncedFn(args); // Call debounced
debouncedFn.cancel(); // Cancel pending
debouncedFn.flush(); // Execute immediately
debouncedFn.pending(); // Check if pendinguseThrottle β Throttle value updates
const throttledValue = useThrottle(value, 100, {
leading: true,
trailing: true,
});Perfect for scroll events, resize handlers, and mouse tracking.
useThrottleCallback β Throttle function calls
const throttledFn = useThrottleCallback(callback, 100);useTimer β Countdown timer with accurate timing
import { useTimer, ms } from "@usefy/use-timer";
const timer = useTimer(ms.minutes(5), {
format: "MM:SS",
autoStart: false,
loop: false,
onComplete: () => console.log("Time's up!"),
});
// Controls
timer.start();
timer.pause();
timer.reset();
timer.addTime(ms.seconds(10));
timer.subtractTime(ms.seconds(5));
// State
timer.formattedTime; // "05:00"
timer.progress; // 0-100
timer.isRunning; // booleanPerfect for countdown timers, Pomodoro apps, kitchen timers, and time-based UIs with smart render optimization.
useLocalStorage β Persistent storage with sync
const [value, setValue, removeValue] = useLocalStorage("key", initialValue, {
serializer: JSON.stringify,
deserializer: JSON.parse,
syncTabs: true,
onError: (error) => console.error(error),
});Supports cross-tab synchronization and custom serialization.
useSessionStorage β Session-scoped storage
const [value, setValue, removeValue] = useSessionStorage("key", initialValue);Data persists during tab lifetime, isolated per tab.
useEventListener β DOM event listener with auto cleanup
// Window resize event (default target)
useEventListener("resize", (e) => {
console.log("Window resized:", window.innerWidth);
});
// Document keydown event
useEventListener(
"keydown",
(e) => {
if (e.key === "Escape") closeModal();
},
document
);
// Element with ref
const buttonRef = useRef<HTMLButtonElement>(null);
useEventListener("click", handleClick, buttonRef);
// With options
useEventListener("scroll", handleScroll, window, {
passive: true,
capture: false,
enabled: isTracking,
});Supports window, document, HTMLElement, and RefObject targets with full TypeScript type inference.
useOnClickOutside β Outside click detection
// Basic usage - close modal on outside click
const modalRef = useRef<HTMLDivElement>(null);
useOnClickOutside(modalRef, () => onClose(), { enabled: isOpen });
// Multiple refs - button and dropdown menu
const buttonRef = useRef<HTMLButtonElement>(null);
const menuRef = useRef<HTMLDivElement>(null);
useOnClickOutside([buttonRef, menuRef], () => setIsOpen(false), {
enabled: isOpen,
});
// With exclude refs
useOnClickOutside(modalRef, onClose, {
excludeRefs: [toastRef], // Clicks on toast won't close modal
});Perfect for modals, dropdowns, popovers, tooltips, and context menus with mouse + touch support.
useClickAnyWhere β Global click detection
useClickAnyWhere(
(event) => {
if (!ref.current?.contains(event.target)) {
closeMenu();
}
},
{ enabled: isOpen }
);Ideal for closing dropdowns, modals, and context menus.
useCopyToClipboard β Clipboard operations
const [copiedText, copy] = useCopyToClipboard({
timeout: 2000,
onSuccess: (text) => toast.success("Copied!"),
onError: (error) => toast.error("Failed to copy"),
});
const success = await copy("text to copy");Modern Clipboard API with automatic fallback for older browsers.
All packages are comprehensively tested using Vitest to ensure reliability and stability.
| Package | Statements | Branches | Functions | Lines |
|---|---|---|---|---|
| use-toggle | 100% | 100% | 100% | 100% |
| use-counter | 100% | 100% | 100% | 100% |
| use-throttle | 100% | 100% | 100% | 100% |
| use-throttle-callback | 100% | 100% | 100% | 100% |
| use-local-storage | 95% | 86% | 100% | 95% |
| use-session-storage | 94% | 79% | 100% | 94% |
| use-debounce-callback | 94% | 83% | 94% | 94% |
| use-click-any-where | 92% | 88% | 100% | 92% |
| use-debounce | 91% | 90% | 67% | 93% |
| use-copy-to-clipboard | 88% | 79% | 86% | 88% |
| use-event-listener | 96% | 91% | 100% | 96% |
| use-on-click-outside | 97% | 93% | 100% | 97% |
| use-timer | 84% | 73% | 94% | 84% |
| Browser | Version |
|---|---|
| Chrome | 66+ |
| Firefox | 63+ |
| Safari | 13.1+ |
| Edge | 79+ |
| IE 11 | Fallback support |
- π¦ npm Organization
- π GitHub Repository
- π Changelog
- π Issue Tracker
MIT Β© mirunamu
Built with β€οΈ by the usefy team
