@pajarrahmansyah/uhooks-js
v1.0.0
Published
A collection of useful React hooks
Maintainers
Readme
uhooks-js
A collection of useful React hooks for everyday use. Zero external dependencies, fully typed with TypeScript, and SSR-safe.
Installation
npm install @pajarrahmansyah/uhooks-jsor
yarn add @pajarrahmansyah/uhooks-jsHooks
| Hook | Description |
|------|-------------|
| useDebounce | Delay updating a value until input stabilizes |
| useLocalStorage | Persist state in localStorage with cross-tab sync |
| useCookie | Read, write, and delete browser cookies |
| useDeviceDetect | Detect mobile, tablet, or desktop device |
| useToggle | Manage a boolean toggle state |
| usePrevious | Get the previous render's value |
| useClickOutside | Detect clicks outside an element |
| useCopyToClipboard | Copy text to the clipboard |
| useMediaQuery | Evaluate a CSS media query in JavaScript |
| useWindowSize | Track window width and height |
| useFetch | Fetch data with loading and error states |
| useEventListener | Attach event listeners declaratively |
useDebounce
Delays updating a value until after a specified delay has elapsed since the last change. Perfect for search inputs and API calls.
import { useDebounce } from "@pajarrahmansyah/uhooks-js";
function SearchComponent() {
const [searchTerm, setSearchTerm] = useState("");
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearchTerm) {
fetchSearchResults(debouncedSearchTerm);
}
}, [debouncedSearchTerm]);
return (
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search..."
/>
);
}API:
useDebounce<T>(value: T, delay?: number): T| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| value | T | — | The value to debounce |
| delay | number | 500 | Delay in milliseconds |
useLocalStorage
Persists state in localStorage with JSON serialization and cross-tab synchronization. API mirrors useState.
import { useLocalStorage } from "@pajarrahmansyah/uhooks-js";
function ThemeToggle() {
const [theme, setTheme, removeTheme] = useLocalStorage("theme", "light");
return (
<div>
<p>Current theme: {theme}</p>
<button onClick={() => setTheme(prev => prev === "light" ? "dark" : "light")}>
Toggle Theme
</button>
<button onClick={removeTheme}>Reset</button>
</div>
);
}API:
useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((prev: T) => T)) => void, () => void]| Parameter | Type | Description |
|-----------|------|-------------|
| key | string | The localStorage key |
| initialValue | T | Fallback value when key is not found |
Returns [storedValue, setValue, removeValue].
useCookie
Reads, writes, and deletes a browser cookie with full options support.
import { useCookie } from "@pajarrahmansyah/uhooks-js";
function AuthComponent() {
const [token, setToken, deleteToken] = useCookie("auth_token");
return (
<div>
<p>Token: {token ?? "none"}</p>
<button onClick={() => setToken("abc123", { expires: 7, secure: true })}>
Set Token
</button>
<button onClick={deleteToken}>Log Out</button>
</div>
);
}API:
useCookie(name: string): [string | null, (value: string, options?: CookieOptions) => void, () => void]CookieOptions:
| Option | Type | Description |
|--------|------|-------------|
| expires | Date \| number | Expiration date or number of days |
| path | string | Cookie path (default: "/") |
| domain | string | Cookie domain |
| secure | boolean | Require HTTPS |
| sameSite | "Strict" \| "Lax" \| "None" | SameSite attribute |
useDeviceDetect
Detects whether the current device is mobile, tablet, or desktop using viewport width and user agent. Updates on resize.
import { useDeviceDetect } from "@pajarrahmansyah/uhooks-js";
function Layout() {
const { isMobile, isTablet, isDesktop, device } = useDeviceDetect();
if (isMobile) return <MobileLayout />;
if (isTablet) return <TabletLayout />;
return <DesktopLayout />;
}API:
useDeviceDetect(): DeviceInfointerface DeviceInfo {
isMobile: boolean;
isTablet: boolean;
isDesktop: boolean;
device: "mobile" | "tablet" | "desktop";
}Breakpoints: mobile < 768px, tablet 768–1024px, desktop > 1024px.
useToggle
Manages a boolean state with a toggle function. Useful for modals, menus, and dark mode switches.
import { useToggle } from "@pajarrahmansyah/uhooks-js";
function Modal() {
const [isOpen, toggle, setOpen] = useToggle(false);
return (
<div>
<button onClick={toggle}>Toggle Modal</button>
<button onClick={() => setOpen(false)}>Close</button>
{isOpen && <div className="modal">Modal content</div>}
</div>
);
}API:
useToggle(initialValue?: boolean): [boolean, () => void, (value: boolean) => void]Returns [value, toggle, setValue].
usePrevious
Returns the value from the previous render. Returns undefined on the first render.
import { usePrevious } from "@pajarrahmansyah/uhooks-js";
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<div>
<p>Now: {count} — Before: {prevCount ?? "—"}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}API:
usePrevious<T>(value: T): T | undefineduseClickOutside
Calls a handler when the user clicks or taps outside the referenced element. Useful for dropdowns, modals, and popovers.
import { useClickOutside } from "@pajarrahmansyah/uhooks-js";
function Dropdown() {
const [isOpen, setIsOpen] = useState(false);
const ref = useClickOutside(() => setIsOpen(false));
return (
<div ref={ref}>
<button onClick={() => setIsOpen(true)}>Open</button>
{isOpen && <ul className="dropdown-menu">...</ul>}
</div>
);
}API:
useClickOutside<T extends HTMLElement>(
handler: (event: MouseEvent | TouchEvent) => void
): React.RefObject<T | null>useCopyToClipboard
Copies text to the clipboard and tracks the last copied value. Resets after a configurable delay.
import { useCopyToClipboard } from "@pajarrahmansyah/uhooks-js";
function CopyButton({ text }: { text: string }) {
const [copiedText, copy] = useCopyToClipboard(2000);
return (
<button onClick={() => copy(text)}>
{copiedText ? "Copied!" : "Copy"}
</button>
);
}API:
useCopyToClipboard(resetDelay?: number): [string | null, (text: string) => Promise<boolean>]| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| resetDelay | number | 2000 | Milliseconds before copiedText resets to null |
useMediaQuery
Evaluates a CSS media query string and returns whether it currently matches. Updates live.
import { useMediaQuery } from "@pajarrahmansyah/uhooks-js";
function App() {
const isMobile = useMediaQuery("(max-width: 768px)");
const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
return (
<div className={prefersDark ? "dark" : "light"}>
{isMobile ? <MobileNav /> : <DesktopNav />}
</div>
);
}API:
useMediaQuery(query: string): booleanuseWindowSize
Tracks the current window dimensions and updates on resize.
import { useWindowSize } from "@pajarrahmansyah/uhooks-js";
function ResponsiveComponent() {
const { width, height } = useWindowSize();
return (
<p>Window: {width} × {height}px</p>
);
}API:
useWindowSize(): WindowSize
interface WindowSize {
width: number;
height: number;
}useFetch
Fetches data from a URL with automatic loading/error state management and cleanup on unmount.
import { useFetch } from "@pajarrahmansyah/uhooks-js";
interface User {
id: number;
name: string;
}
function UserList() {
const { data, error, loading, refetch } = useFetch<User[]>(
"https://api.example.com/users"
);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<ul>
{data?.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
<button onClick={refetch}>Refresh</button>
</div>
);
}API:
useFetch<T>(url: string | null | undefined, options?: RequestInit): FetchState<T>
interface FetchState<T> {
data: T | null;
error: Error | null;
loading: boolean;
refetch: () => void;
}Pass null or undefined as the URL to skip fetching.
useEventListener
Attaches an event listener to a target element declaratively. Cleans up automatically on unmount.
import { useEventListener } from "@pajarrahmansyah/uhooks-js";
function ScrollTracker() {
const [scrollY, setScrollY] = useState(0);
useEventListener("scroll", () => {
setScrollY(window.scrollY);
});
return <p>Scroll position: {scrollY}px</p>;
}// Attach to a specific element
const buttonRef = useRef<HTMLButtonElement>(null);
useEventListener("click", handleClick, buttonRef);API:
useEventListener(
eventName: string,
handler: (event: Event) => void,
element?: React.RefObject<HTMLElement | Document | null>,
options?: boolean | AddEventListenerOptions
): voidelement defaults to window when not provided.
Next.js Compatibility
All hooks are SSR-safe. For Next.js 13+ (App Router), add "use client" to any component that uses hooks:
"use client";
import { useLocalStorage } from "@pajarrahmansyah/uhooks-js";For Pages Router (Next.js 12 and below), use hooks directly without any directive.
TypeScript
This library is written in TypeScript and ships type definitions out of the box. No @types package needed.
Development
See the Development Guide for local setup, watch mode, and testing instructions.
License
MIT
Contributing
Contributions are welcome! Feel free to open an issue or submit a pull request.
