A lightweight set of production-ready React hooks for common UI needs — without extra dependencies.
Save time by eliminating boilerplate for persistent state, clipboard utilities, media queries, viewport detection, and more.
- 18 essential hooks in one package
- TypeScript support out of the box
- Tiny, tree-shakeable build (ESM + types)
- React 16.8+ (hooks API only)
npm install react-lite-hooks
# or
yarn add react-lite-hooks
# or
pnpm add react-lite-hooksimport { usePersistentState } from "react-lite-hooks";
function App() {
const [name, setName] = usePersistentState("name", "");
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
);
}import { useClipboard } from "react-lite-hooks";
function CopyButton() {
const { copy, copied } = useClipboard();
return (
<button onClick={() => copy("Hello world!")}>
{copied ? "Copied!" : "Copy text"}
</button>
);
}import { useMediaQuery } from "react-lite-hooks";
function Component() {
const isDesktop = useMediaQuery("(min-width: 1024px)");
return <p>{isDesktop ? "Desktop view" : "Mobile view"}</p>;
}import { useRef } from "react";
import { useOnScreen } from "react-lite-hooks";
function LazyImage({ src }: { src: string }) {
const ref = useRef(null);
const isVisible = useOnScreen(ref, { rootMargin: "-50px" });
return <img ref={ref} src={isVisible ? src : undefined} alt="" />;
}import { useToggle } from "react-lite-hooks";
function ToggleExample() {
const [isOpen, toggleOpen, setOpen] = useToggle(false);
return (
<div>
<button onClick={toggleOpen}>
{isOpen ? "Close Panel" : "Open Panel"}
</button>
{isOpen && (
<div>
<p>Panel content here.</p>
<button onClick={() => setOpen(false)}>Force Close</button>
</div>
)}
</div>
);
}import { useState, useEffect } from "react";
import { useDebounce } from "react-lite-hooks";
function Search() {
const [searchTerm, setSearchTerm] = useState("");
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearchTerm) {
// Trigger API call here
console.log("Searching for:", debouncedSearchTerm);
}
}, [debouncedSearchTerm]);
return <input onChange={(e) => setSearchTerm(e.target.value)} />;
}import { useRef, useState } from "react";
import { useOnClickOutside } from "react-lite-hooks";
function Modal() {
const ref = useRef(null);
const [isOpen, setIsOpen] = useState(true);
useOnClickOutside(ref, () => setIsOpen(false));
if (!isOpen) return null;
return (
<div ref={ref} style={{ border: "1px solid black", padding: 20 }}>
Click outside to close me
</div>
);
}import { useWindowSize } from "react-lite-hooks";
function ShowSize() {
const { width, height } = useWindowSize();
return (
<p>
Window size: {width} x {height}
</p>
);
}import { useState } from "react";
import { usePrevious } from "react-lite-hooks";
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<div>
<p>
Now: {count}, before: {prevCount}
</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}import { useEventListener } from "react-lite-hooks";
function App() {
useEventListener("keydown", (event) => {
if (event.key === "Escape") {
console.log("Escape pressed!");
}
});
return <div>Press Escape</div>;
}import { useHover } from "react-lite-hooks";
function HoverCard() {
const [hoverRef, isHovered] = useHover();
return <div ref={hoverRef}>{isHovered ? "I am hovered!" : "Hover me"}</div>;
}import { useState } from "react";
import { useInterval } from "react-lite-hooks";
function Timer() {
const [count, setCount] = useState(0);
useInterval(() => {
setCount(count + 1);
}, 1000);
return <p>Count: {count}</p>;
}import { useKeyPress } from "react-lite-hooks";
function KeyListener() {
const isEnterPressed = useKeyPress("Enter");
return <p>{isEnterPressed ? "Enter is pressed!" : "Press Enter"}</p>;
}import { useEffect } from "react";
import { useIsFirstRender } from "react-lite-hooks";
function Component() {
const isFirst = useIsFirstRender();
useEffect(() => {
if (isFirst) {
console.log("Component just mounted");
}
}, [isFirst]);
return <div>Check console</div>;
}import { useState } from "react";
import { useTimeout } from "react-lite-hooks";
function DelayedMessage() {
const [show, setShow] = useState(false);
useTimeout(() => setShow(true), 2000);
return <div>{show ? "Hello after 2s!" : "Waiting..."}</div>;
}import { useState } from "react";
import { useThrottle } from "react-lite-hooks";
function MouseTracker() {
const [x, setX] = useState(0);
const throttledX = useThrottle(x, 200);
return (
<div onMouseMove={(e) => setX(e.clientX)}>
Fast: {x} | Throttled: {throttledX}
</div>
);
}import { useResizeObserver } from "react-lite-hooks";
function ResizableCard() {
const [ref, size] = useResizeObserver<HTMLDivElement>();
return (
<div
ref={ref}
style={{ resize: "both", overflow: "auto", border: "1px solid #ccc" }}
>
Width: {Math.round(size.width)} | Height: {Math.round(size.height)}
</div>
);
}import { useNetworkStatus } from "react-lite-hooks";
function NetworkBanner() {
const isOnline = useNetworkStatus();
return (
<p style={{ color: isOnline ? "green" : "red" }}>
{isOnline ? "You are online" : "You are offline"}
</p>
);
}