@thibault.sh/hooks
Version:
A comprehensive collection of React hooks for browser storage, UI interactions, and more
939 lines (915 loc) • 32 kB
TypeScript
import { RefObject } from 'react';
interface AsyncState<T> {
isLoading: boolean;
error: Error | null;
value: T | null;
}
/**
* Hook that manages async operations with loading, error, and success states.
*
* Provides a clean interface for handling asynchronous functions with automatic
* state management for loading indicators and error handling.
*
* @template T - The type of data returned by the async function
* @param asyncFunction - The async function to execute
*
* @returns An object containing:
* - `execute`: Function to trigger the async operation
* - `status`: Current state with `isLoading`, `error`, and `value` properties
*
* @example
* ```tsx
* const fetchUser = async (id: string) => {
* const response = await fetch(`/api/users/${id}`);
* return response.json();
* };
*
* const { execute, status } = useAsync(fetchUser);
*
* // In your component
* if (status.isLoading) return <div>Loading...</div>;
* if (status.error) return <div>Error: {status.error.message}</div>;
* if (status.value) return <div>User: {status.value.name}</div>;
*
* // Trigger the async operation
* <button onClick={() => execute('user-123')}>Load User</button>
* ```
*
* @see https://thibault.sh/hooks/use-async
*/
declare function useAsync<T>(asyncFunction: (...args: any[]) => Promise<T>): {
execute: (...args: any[]) => Promise<void>;
status: AsyncState<T>;
};
type Handler = (event: MouseEvent | TouchEvent) => void;
/**
* Hook that detects clicks outside a referenced element and executes a callback.
*
* Useful for implementing dropdown menus, modals, or any component that should
* close when the user clicks outside of it. Handles both mouse and touch events.
*
* @template T - The type of HTML element being referenced
* @param ref - React ref object pointing to the element to monitor
* @param handler - Callback function executed when a click occurs outside the element
*
* @example
* ```tsx
* function Dropdown() {
* const [isOpen, setIsOpen] = useState(false);
* const dropdownRef = useRef<HTMLDivElement>(null);
*
* useClickOutside(dropdownRef, () => {
* setIsOpen(false);
* });
*
* return (
* <div ref={dropdownRef}>
* <button onClick={() => setIsOpen(!isOpen)}>
* Toggle Dropdown
* </button>
* {isOpen && (
* <div className="dropdown-menu">
* <p>Dropdown content</p>
* </div>
* )}
* </div>
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-click-outside
*/
declare const useClickOutside: <T extends HTMLElement = HTMLElement>(ref: RefObject<T | null> | null, handler: Handler) => void;
interface ContainerScroll {
scrollTop: number;
scrollLeft: number;
scrollWidth: number;
scrollHeight: number;
clientWidth: number;
clientHeight: number;
isScrolling: boolean;
}
/**
* Hook that tracks scroll position, dimensions, and scrolling state of a container element.
*
* Provides real-time scroll information including position, dimensions, and a debounced
* scrolling indicator that's useful for optimizing scroll-based animations or effects.
*
* @param containerRef - React ref object pointing to the scrollable container element
* @param delay - Delay in milliseconds before setting `isScrolling` to false (default: 150)
*
* @returns An object containing:
* - `scrollTop`: Vertical scroll position in pixels
* - `scrollLeft`: Horizontal scroll position in pixels
* - `scrollWidth`: Total scrollable width of the content
* - `scrollHeight`: Total scrollable height of the content
* - `clientWidth`: Visible width of the container
* - `clientHeight`: Visible height of the container
* - `isScrolling`: Boolean indicating if the user is currently scrolling (debounced)
*
* @example
* ```tsx
* const containerRef = useRef<HTMLDivElement>(null);
* const scroll = useContainerScroll(containerRef, 200);
*
* return (
* <div ref={containerRef} className="scrollable-container">
* <div>Scroll position: {scroll.scrollTop}px</div>
* <div>Container size: {scroll.clientWidth}x{scroll.clientHeight}</div>
* <div>Content size: {scroll.scrollWidth}x{scroll.scrollHeight}</div>
* {scroll.isScrolling && <div>Currently scrolling...</div>}
*
* <div style={{ height: '2000px' }}>Long content...</div>
* </div>
* );
* ```
* @see https://thibault.sh/hooks/use-container-scroll
*/
declare function useContainerScroll(containerRef: RefObject<HTMLElement | null>, delay?: number): ContainerScroll;
/**
* Options for configuring cookie behavior
*/
interface CookieOptions {
/** Number of days until the cookie expires (default: 7) */
days?: number;
/** Cookie path (default: "/") */
path?: string;
/** Cookie domain */
domain?: string;
/** Whether the cookie requires HTTPS (default: false) */
secure?: boolean;
/** SameSite cookie attribute (default: "Lax") */
sameSite?: "Strict" | "Lax" | "None";
}
/**
* Hook for managing state that persists in browser cookies with SSR support.
*
* Provides a React state-like interface for reading, writing, and deleting cookies.
* Automatically handles encoding/decoding, error handling, and server-side rendering compatibility.
*
* @param name - The name of the cookie to manage
* @param initialValue - The default value to use when no cookie exists or on server-side
*
* @returns A tuple containing:
* - `value`: Current cookie value as string, or null if not set
* - `setCookie`: Function to update the cookie with optional configuration
* - `deleteCookie`: Function to remove the cookie from the browser
*
* @example
* ```tsx
* const [theme, setTheme, deleteTheme] = useCookieState('theme', 'light');
*
* // Read current value
* console.log(theme); // 'light' or saved value
*
* // Update cookie with default options (7 days expiry)
* setTheme('dark');
*
* // Update with custom options
* setTheme('dark', {
* days: 30,
* secure: true,
* sameSite: 'Strict'
* });
*
* // Remove the cookie
* deleteTheme();
* ```
*
* @example
* ```tsx
* // User preferences with longer expiry
* const [userPrefs, setUserPrefs] = useCookieState('preferences', '{}');
*
* const updatePreference = (key: string, value: any) => {
* const prefs = JSON.parse(userPrefs || '{}');
* prefs[key] = value;
* setUserPrefs(JSON.stringify(prefs), { days: 365 });
* };
* ```
* @see https://thibault.sh/hooks/use-cookie-state
*/
declare function useCookieState(name: string, initialValue: string): [string | null, (newValue: string, options?: CookieOptions) => void, () => void];
/**
* Hook that creates a countdown timer to a target date with automatic updates.
*
* Provides real-time countdown values that update at a specified interval.
* Returns zero values when the target date has passed.
*
* @param countDownDate - Target date as a timestamp in milliseconds (e.g., `new Date('2024-12-31').getTime()`)
* @param refreshRate - Update interval in milliseconds (defaults to 1000ms for 1-second updates)
*
* @returns A readonly tuple `[days, hours, minutes, seconds]` representing time remaining
*
* @example
* ```tsx
* const targetDate = new Date('2024-12-31 23:59:59').getTime();
* const [days, hours, minutes, seconds] = useCountdown(targetDate);
*
* return (
* <div>
* {days}d {hours}h {minutes}m {seconds}s remaining
* </div>
* );
* ```
*
* @example
* // Custom refresh rate (every 100ms for smoother animation)
* const [days, hours, minutes, seconds] = useCountdown(targetDate, 100);
* @see https://thibault.sh/hooks/use-countdown
*/
declare function useCountdown(countDownDate: number, refreshRate?: number): number[] | readonly [number, number, number, number];
/**
* Hook that tracks the state of a CSS media query and updates when it changes.
*
* Provides a reactive way to respond to viewport changes, screen sizes, or any
* CSS media query conditions in your React components.
*
* @param query - The CSS media query string to track (e.g., "(min-width: 768px)")
*
* @returns Boolean indicating whether the media query currently matches
*
* @example
* ```tsx
* function ResponsiveComponent() {
* const isMobile = useMediaQuery('(max-width: 768px)');
* const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
* const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
*
* return (
* <div>
* {isMobile && <MobileLayout />}
* {isTablet && <TabletLayout />}
* {isDarkMode ? <DarkTheme /> : <LightTheme />}
* </div>
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-media-query
*/
declare const useMediaQuery: (query: string) => boolean;
/**
* Hook that detects when an element is being hovered over.
*
* Provides a simple way to track hover state for any HTML element, with support
* for both internal ref management and external ref usage.
*
* @template T - The type of HTML element being monitored (extends HTMLElement)
* @param _ref - Optional React ref object for the element to monitor. If not provided,
* the hook will create and return its own ref.
*
* @returns A tuple containing:
* - `ref`: React ref object to attach to the target element
* - `isHovered`: Boolean indicating whether the element is currently being hovered
*
* @example
* ```tsx
* // Using the hook's internal ref
* function HoverButton() {
* const [ref, isHovered] = useHover<HTMLButtonElement>();
*
* return (
* <button
* ref={ref}
* style={{ backgroundColor: isHovered ? 'lightblue' : 'white' }}
* >
* {isHovered ? 'Hovered!' : 'Hover me'}
* </button>
* );
* }
*
* // Using an external ref
* function HoverDiv() {
* const myRef = useRef<HTMLDivElement>(null);
* const [, isHovered] = useHover(myRef);
*
* return (
* <div ref={myRef}>
* {isHovered && <span>You're hovering!</span>}
* </div>
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-hover
*/
declare const useHover: <T extends HTMLElement>(_ref?: RefObject<T | null> | null) => [RefObject<T>, boolean];
/**
* Hook that detects when a specific key is pressed and held down.
*
* Tracks the current pressed state of a keyboard key, returning true while
* the key is held down and false when released.
*
* @param targetKey - The key to detect (e.g., "Enter", "Escape", "ArrowUp", "a")
*
* @returns Boolean indicating if the target key is currently pressed
*
* @example
* ```tsx
* function GameControls() {
* const isSpacePressed = useKeyPress(' ');
* const isEnterPressed = useKeyPress('Enter');
* const isArrowUpPressed = useKeyPress('ArrowUp');
*
* return (
* <div>
* <p>Space: {isSpacePressed ? 'Pressed' : 'Released'}</p>
* <p>Enter: {isEnterPressed ? 'Pressed' : 'Released'}</p>
* <p>Arrow Up: {isArrowUpPressed ? 'Pressed' : 'Released'}</p>
*
* {isSpacePressed && <div>🚀 Boost active!</div>}
* </div>
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-key-press
*/
declare function useKeyPress(targetKey: string): boolean;
type KeyCombo = string[];
/**
* Hook that detects when a specific combination of keys is pressed simultaneously.
*
* Useful for implementing keyboard shortcuts, hotkeys, or complex key combinations
* in your React components. The hook tracks all currently pressed keys and returns
* true when all keys in the target combination are active.
*
* @param targetCombo - Array of key names that make up the combination
*
* @returns Boolean indicating if all keys in the combination are currently pressed
*
* @example
* ```tsx
* function App() {
* const isSaveCombo = useKeyCombo(['Control', 's']);
* const isUndoCombo = useKeyCombo(['Control', 'z']);
* const isComplexCombo = useKeyCombo(['Control', 'Shift', 'p']);
*
* useEffect(() => {
* if (isSaveCombo) {
* console.log('Save shortcut pressed!');
* // Handle save action
* }
* }, [isSaveCombo]);
*
* return (
* <div>
* <p>Press Ctrl+S to save</p>
* <p>Save combo active: {isSaveCombo ? 'Yes' : 'No'}</p>
* </div>
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-key-combo
*/
declare function useKeyCombo(targetCombo: KeyCombo): boolean;
/**
* Configuration options for the useLongPress hook
*/
interface LongPressOptions {
/** Duration in milliseconds before long press is triggered (default: 400) */
delay?: number;
/** Whether to disable context menu on long press (default: true) */
preventContext?: boolean;
/** Callback fired when a normal press (shorter than delay) is completed */
onPress?: () => void;
/** Callback fired when a long press is successfully triggered */
onLongPress?: () => void;
/** Callback fired when a long press is canceled before completion */
onLongPressCanceled?: () => void;
}
/**
* Hook that handles both normal press and long press interactions with progress tracking.
*
* Provides event handlers for detecting short taps vs long presses, with smooth progress
* animation and customizable timing. Works with both mouse and touch events.
*
* @param options - Configuration options for the long press behavior
* @param options.delay - Duration in milliseconds before triggering long press (default: 400)
* @param options.preventContext - Whether to prevent context menu on long press (default: true)
* @param options.onPress - Callback for normal press (when released before delay)
* @param options.onLongPress - Callback for successful long press (when delay is reached)
* @param options.onLongPressCanceled - Callback when long press is interrupted
*
* @returns Object containing:
* - `handlers`: Event handlers to spread on your element
* - `state`: Current press state with `isPressed`, `isLongPressed`, and `progress` (0-1)
*
* @example
* ```tsx
* function DeleteButton({ onDelete }) {
* const { handlers, state } = useLongPress({
* delay: 1000,
* onPress: () => console.log('Quick tap - no action'),
* onLongPress: onDelete,
* onLongPressCanceled: () => console.log('Canceled deletion')
* });
*
* return (
* <button {...handlers} className={state.isPressed ? 'pressing' : ''}>
* {state.isLongPressed
* ? 'Deleting...'
* : `Hold to delete (${Math.round(state.progress * 100)}%)`
* }
* </button>
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-long-press
*/
declare function useLongPress(options?: LongPressOptions): {
/** Event handlers to attach to the target element */
handlers: {
onMouseDown: (event: React.TouchEvent | React.MouseEvent) => void;
onMouseUp: (event: React.TouchEvent | React.MouseEvent) => void;
onMouseLeave: (event: React.TouchEvent | React.MouseEvent) => void;
onTouchStart: (event: React.TouchEvent | React.MouseEvent) => void;
onTouchEnd: (event: React.TouchEvent | React.MouseEvent) => void;
onTouchCancel: (event: React.TouchEvent | React.MouseEvent) => void;
};
/** Current state of the press interaction */
state: {
isPressed: boolean;
isLongPressed: boolean;
progress: number;
};
};
interface WindowSize {
width: number;
height: number;
}
/**
* Hook that tracks the browser window dimensions and updates on resize.
*
* Automatically listens for window resize events and provides the current
* width and height. Safe for SSR environments by checking for window availability.
*
* @returns An object containing:
* - `width`: Current window inner width in pixels
* - `height`: Current window inner height in pixels
*
* @example
* ```tsx
* function ResponsiveComponent() {
* const { width, height } = useWindowSize();
*
* return (
* <div>
* <p>Window size: {width} x {height}</p>
* {width < 768 ? (
* <MobileLayout />
* ) : (
* <DesktopLayout />
* )}
* </div>
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-window-size
*/
declare function useWindowSize(): WindowSize;
interface ScrollPosition {
x: number;
y: number;
}
/**
* Hook that tracks the current window scroll position in real-time.
*
* Automatically updates when the user scrolls and handles SSR scenarios
* by safely checking for window availability.
*
* @returns An object containing:
* - `x`: Horizontal scroll position in pixels
* - `y`: Vertical scroll position in pixels
*
* @example
* ```tsx
* function ScrollIndicator() {
* const { x, y } = useScrollPosition();
*
* return (
* <div className="scroll-info">
* <p>Horizontal: {x}px</p>
* <p>Vertical: {y}px</p>
* {y > 100 && (
* <button onClick={() => window.scrollTo(0, 0)}>
* Back to Top
* </button>
* )}
* </div>
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-scroll-position
*/
declare function useScrollPosition(): ScrollPosition;
interface ElementSize {
width: number;
height: number;
}
/**
* Hook that tracks an element's dimensions and updates when the element is resized.
*
* Uses ResizeObserver to efficiently monitor size changes and provides real-time
* width and height measurements. Automatically handles cleanup and provides
* initial measurements immediately.
*
* @param elementRef - React ref object pointing to the target HTML element
*
* @returns An object containing:
* - `width`: Current width of the element in pixels
* - `height`: Current height of the element in pixels
*
* @example
* ```tsx
* function ResizableComponent() {
* const elementRef = useRef<HTMLDivElement>(null);
* const { width, height } = useElementSize(elementRef);
*
* return (
* <div>
* <div ref={elementRef} style={{ resize: 'both', overflow: 'auto' }}>
* Resizable content
* </div>
* <p>Size: {width} x {height}px</p>
* </div>
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-element-size
*/
declare function useElementSize<T extends HTMLElement>(elementRef: RefObject<T | null> | null): ElementSize;
interface IntersectionOptions extends IntersectionObserverInit {
freezeOnceVisible?: boolean;
}
/**
* Hook that tracks when an element enters or leaves the viewport using the Intersection Observer API.
*
* Useful for implementing lazy loading, infinite scrolling, animations on scroll,
* or tracking visibility of elements for analytics purposes.
*
* @param elementRef - React ref object pointing to the target element to observe
* @param options - Configuration options for the intersection observer:
* - `threshold`: Number or array defining at what percentage of visibility the callback should trigger (0-1)
* - `root`: Element used as viewport for checking visibility (defaults to browser viewport)
* - `rootMargin`: Margin around the root element (CSS-like syntax, e.g., "10px 20px")
* - `freezeOnceVisible`: If true, stops observing once element becomes visible (useful for one-time animations)
*
* @returns IntersectionObserverEntry object containing visibility information, or null if element not found
*
* @example
* ```tsx
* function LazyImage({ src, alt }: { src: string; alt: string }) {
* const imgRef = useRef<HTMLImageElement>(null);
* const entry = useIntersectionObserver(imgRef, {
* threshold: 0.1,
* freezeOnceVisible: true
* });
*
* const isVisible = entry?.isIntersecting;
*
* return (
* <img
* ref={imgRef}
* src={isVisible ? src : undefined}
* alt={alt}
* style={{ opacity: isVisible ? 1 : 0 }}
* />
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-intersection-observer
*/
declare function useIntersectionObserver<T extends HTMLElement>(elementRef: RefObject<T | null> | null, { threshold, root, rootMargin, freezeOnceVisible }?: IntersectionOptions): IntersectionObserverEntry | null;
interface ResizeObserverEntry {
contentRect: DOMRectReadOnly;
contentBoxSize: ReadonlyArray<ResizeObserverSize>;
borderBoxSize: ReadonlyArray<ResizeObserverSize>;
devicePixelContentBoxSize: ReadonlyArray<ResizeObserverSize>;
target: Element;
}
/**
* Hook that observes element size changes using the ResizeObserver API.
*
* Provides detailed resize information including content rect, box sizes, and device pixel ratios.
* Automatically cleans up the observer when the component unmounts or the ref changes.
*
* @param elementRef - React ref object pointing to the target element to observe
*
* @returns ResizeObserverEntry with detailed size information, or null if no element or no resize has occurred
* - `contentRect`: The content rectangle of the element
* - `contentBoxSize`: Array of content box dimensions
* - `borderBoxSize`: Array of border box dimensions
* - `devicePixelContentBoxSize`: Array of device pixel content box dimensions
* - `target`: The observed element
*
* @example
* ```tsx
* function ResponsiveComponent() {
* const containerRef = useRef<HTMLDivElement>(null);
* const resizeEntry = useResizeObserver(containerRef);
*
* const width = resizeEntry?.contentRect.width ?? 0;
* const height = resizeEntry?.contentRect.height ?? 0;
*
* return (
* <div ref={containerRef}>
* <p>Size: {width} x {height}</p>
* <div>Content adapts based on container size</div>
* </div>
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-resize-observer
*/
declare function useResizeObserver<T extends HTMLElement>(elementRef: RefObject<T | null> | null): ResizeObserverEntry | null;
/**
* Hook that debounces a value, delaying updates until after the specified delay.
*
* Useful for optimizing performance in scenarios like search inputs, API calls,
* or any situation where you want to limit how often a value changes.
*
* @template T - The type of the value being debounced
* @param value - The value to debounce
* @param delay - The delay in milliseconds before the value updates
*
* @returns The debounced value that only updates after the delay period
*
* @example
* ```tsx
* function SearchInput() {
* const [searchTerm, setSearchTerm] = useState('');
* const debouncedSearchTerm = useDebounce(searchTerm, 300);
*
* useEffect(() => {
* if (debouncedSearchTerm) {
* // This will only run 300ms after the user stops typing
* searchAPI(debouncedSearchTerm);
* }
* }, [debouncedSearchTerm]);
*
* return (
* <input
* value={searchTerm}
* onChange={(e) => setSearchTerm(e.target.value)}
* placeholder="Search..."
* />
* );
* }
* ```
* @see https://thibault.sh/hooks/use-debounce
*/
declare function useDebounce<T>(value: T, delay: number): T;
/**
* Hook that throttles a value, limiting how often it can update.
*
* Unlike debouncing which delays execution until after a quiet period, throttling
* ensures the value updates at most once per specified interval, making it ideal
* for scroll events, resize handlers, or any high-frequency updates.
*
* @template T - The type of the value being throttled
* @param value - The value to throttle
* @param interval - The minimum time interval between updates in milliseconds
*
* @returns The throttled value that updates at most once per interval
*
* @example
* ```tsx
* function ScrollTracker() {
* const [scrollY, setScrollY] = useState(0);
* const throttledScrollY = useThrottle(scrollY, 100);
*
* useEffect(() => {
* const handleScroll = () => setScrollY(window.scrollY);
* window.addEventListener('scroll', handleScroll);
* return () => window.removeEventListener('scroll', handleScroll);
* }, []);
*
* useEffect(() => {
* // This will only run at most once every 100ms
* console.log('Throttled scroll position:', throttledScrollY);
* }, [throttledScrollY]);
*
* return <div>Scroll position: {throttledScrollY}px</div>;
* }
* ```
*
* @see https://thibault.sh/hooks/use-throttle
*/
declare function useThrottle<T>(value: T, interval: number): T;
/**
* Hook that creates a setInterval that automatically cleans up on unmount.
*
* Provides a declarative way to use intervals in React components with proper
* cleanup and the ability to pause/resume by passing null as the delay.
*
* @param callback - The function to execute on each interval tick
* @param delay - The delay in milliseconds between executions, or null to pause the interval
*
* @example
* ```tsx
* function Timer() {
* const [count, setCount] = useState(0);
* const [isRunning, setIsRunning] = useState(true);
*
* // Increment count every second when running
* useInterval(() => {
* setCount(count => count + 1);
* }, isRunning ? 1000 : null);
*
* return (
* <div>
* <p>Count: {count}</p>
* <button onClick={() => setIsRunning(!isRunning)}>
* {isRunning ? 'Pause' : 'Resume'}
* </button>
* </div>
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-interval
*/
declare function useInterval(callback: () => void, delay: number | null): void;
type EventMap = WindowEventMap & HTMLElementEventMap & DocumentEventMap;
/**
* Hook that adds an event listener to a target element with automatic cleanup.
*
* Automatically handles adding and removing event listeners, ensuring proper cleanup
* when the component unmounts or dependencies change. Supports all standard DOM events
* on window, document, or specific HTML elements.
*
* @template K - The event type key from the event map
* @param eventName - The name of the event to listen for (e.g., 'click', 'keydown', 'resize')
* @param handler - The event handler function that will be called when the event fires
* @param element - Optional ref to the target element. Defaults to window if not provided
* @param options - Optional event listener options (capture, once, passive, etc.)
*
* @example
* ```tsx
* function Component() {
* const buttonRef = useRef<HTMLButtonElement>(null);
*
* // Listen for clicks on a specific element
* useEventListener('click', (e) => {
* console.log('Button clicked!', e);
* }, buttonRef);r
*
* // Listen for window resize events
* useEventListener('resize', () => {
* console.log('Window resized');
* });
*
* // Listen for escape key presses
* useEventListener('keydown', (e) => {
* if (e.key === 'Escape') {
* console.log('Escape pressed');
* }
* });
*
* return <button ref={buttonRef}>Click me</button>;
* }
* ```
*
* @see https://thibault.sh/hooks/use-event-listener
*/
declare function useEventListener<K extends keyof EventMap, T extends HTMLElement>(eventName: K, handler: (event: EventMap[K]) => void, element?: RefObject<T | null> | null, options?: boolean | AddEventListenerOptions): void;
/**
* Hook that manages state synchronized with localStorage.
*
* Provides a useState-like interface that automatically persists state changes
* to localStorage and initializes from stored values on mount. Handles SSR
* compatibility and JSON serialization/deserialization automatically.
*
* @template T - The type of the value being stored
* @param key - The localStorage key to use for persistence
* @param initialValue - The default value to use if no stored value exists
*
* @returns A tuple containing:
* - Current stored value (T)
* - Setter function that updates both state and localStorage
*
* @example
* ```tsx
* function UserPreferences() {
* const [theme, setTheme] = useLocalStorageState('theme', 'light');
* const [settings, setSettings] = useLocalStorageState('settings', {
* notifications: true,
* language: 'en'
* });
*
* return (
* <div>
* <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
* Current theme: {theme}
* </button>
*
* <button onClick={() => setSettings(prev => ({
* ...prev,
* notifications: !prev.notifications
* }))}>
* Notifications: {settings.notifications ? 'On' : 'Off'}
* </button>
* </div>
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-local-storage-state
*/
declare function useLocalStorageState<T>(key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void];
/**
* Hook that manages state synchronized with sessionStorage.
*
* Provides persistent state that survives page refreshes but is cleared when
* the browser tab is closed. Automatically handles JSON serialization/deserialization
* and provides SSR-safe initialization.
*
* @template T - The type of the stored value
* @param key - The sessionStorage key to store the value under
* @param initialValue - The default value used when no stored value exists
*
* @returns A tuple containing:
* - The current stored value (or initial value if none exists)
* - A setter function that updates both state and sessionStorage
*
* @example
* ```tsx
* function UserPreferences() {
* const [theme, setTheme] = useSessionStorageState('theme', 'light');
* const [sidebarOpen, setSidebarOpen] = useSessionStorageState('sidebar', true);
*
* return (
* <div>
* <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
* Current theme: {theme}
* </button>
* <button onClick={() => setSidebarOpen(!sidebarOpen)}>
* Sidebar: {sidebarOpen ? 'Open' : 'Closed'}
* </button>
* </div>
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-session-storage-state
*/
declare function useSessionStorageState<T>(key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void];
/**
* Hook that manages state synchronized with URL query parameters.
*
* Automatically persists state to the URL and keeps it in sync with browser
* navigation (back/forward buttons). Perfect for shareable URLs and maintaining
* state across page refreshes.
*
* @template T - The type of the state value
* @param key - The query parameter key to use in the URL
* @param initialValue - Default value when the parameter doesn't exist
* @param options - Configuration options for serialization
* @param options.serialize - Custom function to convert value to string (defaults to JSON.stringify)
* @param options.deserialize - Custom function to parse string back to value (defaults to JSON.parse)
*
* @returns A tuple containing:
* - Current state value (synced with URL)
* - Setter function (updates both state and URL)
*
* @example
* ```tsx
* function SearchPage() {
* const [query, setQuery] = useQueryParamsState('q', '');
* const [filters, setFilters] = useQueryParamsState('filters', { category: 'all' });
*
* return (
* <div>
* <input
* value={query}
* onChange={(e) => setQuery(e.target.value)}
* placeholder="Search..."
* />
* <select
* value={filters.category}
* onChange={(e) => setFilters({ ...filters, category: e.target.value })}
* >
* <option value="all">All</option>
* <option value="books">Books</option>
* </select>
* </div>
* );
* }
* ```
*
* @see https://thibault.sh/hooks/use-query-params-state
*/
declare function useQueryParamsState<T>(key: string, initialValue: T, options?: {
serialize?: (value: T) => string;
deserialize?: (value: string) => T;
}): [T, (value: T | ((val: T) => T)) => void];
export { useAsync, useClickOutside, useContainerScroll, useCookieState, useCountdown, useDebounce, useElementSize, useEventListener, useHover, useIntersectionObserver, useInterval, useKeyCombo, useKeyPress, useLocalStorageState, useLongPress, useMediaQuery, useQueryParamsState, useResizeObserver, useScrollPosition, useSessionStorageState, useThrottle, useWindowSize };