A collection of high-quality, fully tested React hooks for common use cases. Written in TypeScript with comprehensive documentation and examples.
- Qazuor React Custom Hooks
- 📦 20+ Custom Hooks
- đź”’ Type-safe with TypeScript
- 📚 Comprehensive documentation
- âś… Fully tested
- 🎯 Zero dependencies
- 🌳 Tree-shakeable
- đź’» SSR compatible
# Using npm
npm install @qazuor/react-hooks
# Using yarn
yarn add @qazuor/react-hooks
# Using pnpm
pnpm add @qazuor/react-hooks
Manages a boolean state with convenient methods.
import { useBoolean } from '@qazuor/react-hooks';
function Modal() {
const { value: isOpen, setTrue: open, setFalse: close } = useBoolean(false);
return (
<>
<button onClick={open} type="button">Open Modal</button>
{isOpen && (
<div className="modal">
<button onClick={close} type="button">Close</button>
<h1>Modal Content</h1>
</div>
)}
</>
);
}
Manages a toggleable boolean state with persistence options.
import { useToggle } from '@qazuor/react-hooks';
function ThemeToggle() {
const { value: isDark, toggle } = useToggle({
initialValue: false,
persist: true,
storageKey: 'theme-preference'
});
return (
<button onClick={toggle} type="button">
Current theme: {isDark ? 'Dark' : 'Light'}
</button>
);
}
Implements a FIFO queue with state management.
import { useQueue } from '@qazuor/react-hooks';
function TaskQueue() {
const { enqueue, dequeue, peek, size, isEmpty } = useQueue<string>();
return (
<div>
<button onClick={() => enqueue(`Task ${size + 1}`)} type="button">
Add Task
</button>
<button onClick={dequeue} disabled={isEmpty} type="button">
Process Next
</button>
<p>Next task: {peek() || 'No tasks'}</p>
<p>Queue size: {size}</p>
</div>
);
}
Execute a callback after a delay with control methods.
import { useTimeout } from '@qazuor/react-hooks';
function AutoDismiss() {
const [visible, setVisible] = useState(true);
const { isPending, reset } = useTimeout({
callback: () => setVisible(false),
delay: 3000
});
return visible && (
<div>
<p>I will disappear in 3 seconds!</p>
<button onClick={reset} type="button">Reset Timer</button>
</div>
);
}
Execute a callback at regular intervals with pause/resume functionality.
import { useInterval } from '@qazuor/react-hooks';
function Counter() {
const [count, setCount] = useState(0);
const { isRunning, start, pause } = useInterval({
callback: () => setCount(c => c + 1),
delay: 1000
});
return (
<div>
<p>Count: {count}</p>
<button onClick={isRunning ? pause : start} type="button">
{isRunning ? 'Pause' : 'Start'}
</button>
</div>
);
}
Enhanced interval with random delay options and more control.
import { useHandledInterval } from '@qazuor/react-hooks';
function RandomTimer() {
const [count, setCount] = useState(0);
const { isRunning, start, pause, reset } = useHandledInterval({
callback: () => setCount(c => c + 1),
delay: 1000,
random: true,
minDelay: 500
});
return (
<div>
<p>Random intervals count: {count}</p>
<button onClick={isRunning ? pause : start} type="button">
{isRunning ? 'Pause' : 'Start'}
</button>
<button onClick={reset} type="button">Reset</button>
</div>
);
}
Debounce a value with configurable delay.
import { useDebounce } from '@qazuor/react-hooks';
function SearchInput() {
const [value, setValue] = useState('');
const debouncedValue = useDebounce(value, 500);
useEffect(() => {
// API call with debouncedValue
console.log('Searching:', debouncedValue);
}, [debouncedValue]);
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Search..."
/>
);
}
Persist state in localStorage with type safety.
import { useLocalStorage } from '@qazuor/react-hooks';
function UserPreferences() {
const [preferences, setPreferences] = useLocalStorage('user-prefs', {
theme: 'light',
fontSize: 16
});
return (
<div>
<button
onClick={() => setPreferences(p => ({ ...p, theme: p.theme === 'light' ? 'dark' : 'light' }))}
type="button"
>
Toggle Theme
</button>
<select
value={preferences.fontSize}
onChange={(e) => setPreferences(p => ({ ...p, fontSize: Number(e.target.value) }))}
>
<option value="14">Small</option>
<option value="16">Medium</option>
<option value="18">Large</option>
</select>
</div>
);
}
Persist state in sessionStorage.
import { useSessionStorage } from '@qazuor/react-hooks';
function FormWithAutosave() {
const [formData, setFormData] = useSessionStorage('form-draft', {
title: '',
content: ''
});
return (
<form>
<input
value={formData.title}
onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
placeholder="Title"
/>
<textarea
value={formData.content}
onChange={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))}
placeholder="Content"
/>
</form>
);
}
Copy text to clipboard with status feedback.
import { useCopyToClipboard } from '@qazuor/react-hooks';
function ShareButton() {
const { copy, copied, error } = useCopyToClipboard();
const url = window.location.href;
return (
<button
onClick={() => copy(url)}
type="button"
className={copied ? 'success' : error ? 'error' : ''}
>
{copied ? 'Copied!' : error ? 'Failed to copy' : 'Copy URL'}
</button>
);
}
React to media query changes.
import { useMediaQuery } from '@qazuor/react-hooks';
function ResponsiveLayout() {
const isMobile = useMediaQuery('(max-width: 768px)');
const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
return (
<div className={`layout ${isMobile ? 'mobile' : 'desktop'} ${prefersDark ? 'dark' : 'light'}`}>
<p>Current layout: {isMobile ? 'Mobile' : 'Desktop'}</p>
<p>Theme preference: {prefersDark ? 'Dark' : 'Light'}</p>
</div>
);
}
Track network connectivity status.
import { useNetworkState } from '@qazuor/react-hooks';
function NetworkIndicator() {
const { online, type, rtt } = useNetworkState();
return (
<div>
<div className={`status-dot ${online ? 'online' : 'offline'}`} />
{online && (
<>
<p>Connection: {type}</p>
<p>Latency: {rtt}ms</p>
</>
)}
</div>
);
}
Track document visibility state.
import { useVisibilityChange } from '@qazuor/react-hooks';
function VideoPlayer() {
const videoRef = useRef<HTMLVideoElement>(null);
const { isVisible } = useVisibilityChange({
onHidden: () => videoRef.current?.pause(),
onVisible: () => videoRef.current?.play()
});
return (
<div>
<video ref={videoRef} />
<p>Video is {isVisible ? 'visible' : 'hidden'}</p>
</div>
);
}
Track window width with debouncing.
import { useWindowWidth } from '@qazuor/react-hooks';
function ResponsiveComponent() {
const { width } = useWindowWidth({
debounceDelay: 250,
onChange: (w) => console.log(`Window width changed to ${w}px`)
});
return (
<div>
<p>Window width: {width}px</p>
{width > 1024 ? (
<h1>Desktop View</h1>
) : width > 768 ? (
<h2>Tablet View</h2>
) : (
<h3>Mobile View</h3>
)}
</div>
);
}
Detect clicks outside an element.
import { useClickOutside } from '@qazuor/react-hooks';
function Dropdown() {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef(null);
useClickOutside(ref, () => setIsOpen(false));
return (
<div ref={ref}>
<button onClick={() => setIsOpen(true)} type="button">
Toggle Dropdown
</button>
{isOpen && (
<ul className="dropdown-menu">
<li>Option 1</li>
<li>Option 2</li>
</ul>
)}
</div>
);
}
Track user idle state.
import { useIdleness } from '@qazuor/react-hooks';
function IdleMonitor() {
const { isIdle, reset } = useIdleness({
timeout: 5000,
onIdleChange: (idle) => {
if (idle) {
console.log('User is idle');
} else {
console.log('User is active');
}
}
});
return (
<div>
<p>User is currently {isIdle ? 'idle' : 'active'}</p>
<button onClick={reset} type="button">Reset Idle Timer</button>
</div>
);
}
Detect when user attempts to leave page.
import { usePageLeave } from '@qazuor/react-hooks';
function ExitIntent() {
const { hasLeft } = usePageLeave({
onLeave: () => console.log('Mouse left the page'),
threshold: 10
});
return hasLeft && (
<div className="exit-popup">
<h2>Wait! Don't leave yet...</h2>
<p>Would you like to subscribe to our newsletter?</p>
</div>
);
}
Prevent body scrolling.
import { useLockBodyScroll } from '@qazuor/react-hooks';
function Modal({ isOpen }: { isOpen: boolean }) {
const { isLocked, lock, unlock } = useLockBodyScroll({
lockImmediately: isOpen,
preservePosition: true
});
useEffect(() => {
if (isOpen) {
lock();
} else {
unlock();
}
}, [isOpen, lock, unlock]);
return isOpen && (
<div className="modal">
<h2>Modal Content</h2>
<p>Body scroll is {isLocked ? 'locked' : 'unlocked'}</p>
</div>
);
}
Measure DOM elements.
import { useMeasure } from '@qazuor/react-hooks';
function ResizableBox() {
const { ref, size } = useMeasure();
return (
<div
ref={ref}
style={{
resize: 'both',
overflow: 'auto',
minWidth: '100px',
minHeight: '100px',
border: '1px solid black'
}}
>
<p>Width: {size.width}px</p>
<p>Height: {size.height}px</p>
</div>
);
}
Debug values with console logging.
import { useLogger } from '@qazuor/react-hooks';
function DebugComponent() {
const [count, setCount] = useState(0);
useLogger('Counter Value', count, {
level: 'info',
timestamp: true,
formatter: (label, value) => `${label}: ${value} (${new Date().toISOString()})`
});
return (
<button onClick={() => setCount(c => c + 1)} type="button">
Increment ({count})
</button>
);
}
All hooks are written in TypeScript and include comprehensive type definitions. Generic types are used where appropriate to ensure type safety.
interface UserPreferences {
theme: 'light' | 'dark';
fontSize: number;
}
// Type-safe localStorage hook
const [preferences, setPreferences] = useLocalStorage<UserPreferences>('prefs', {
theme: 'light',
fontSize: 16
});
// TypeScript knows the correct types
preferences.theme; // 'light' | 'dark'
preferences.fontSize; // number
The library supports all modern browsers. Some hooks may require specific browser features - check individual hook documentation for details.
Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
This project is licensed under the MIT License - see the LICENSE file for details.
Leandro Asrilevich (@qazuor)