@thibault.sh/hooks
Version:
A comprehensive collection of React hooks for browser storage, UI interactions, and more
307 lines (283 loc) • 12.4 kB
text/typescript
import { RefObject } from 'react';
interface AsyncState<T> {
isLoading: boolean;
error: Error | null;
value: T | null;
}
/**
* Hook that handles async operations with loading and error states
* @param asyncFunction - Async function to execute
* @returns Object containing execute function, loading state, error, and value
*/
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 handles click outside of the referenced element
* @param ref - React ref object for the element to monitor
* @param handler - Callback function to execute when click outside occurs
*/
declare const useClickOutside: <T extends HTMLElement = HTMLElement>(ref: RefObject<T | null> | null, handler: Handler) => void;
/**
* Hook that tracks state of a CSS media query
* @param query - CSS media query string
* @returns boolean indicating if the media query matches
*/
declare const useMediaQuery: (query: string) => boolean;
/**
* Hook that detects hover state on an element
* @param ref - (optional) React ref object for the element to monitor
* @returns Tuple containing ref to attach to element and boolean indicating hover state
*/
declare const useHover: <T extends HTMLElement>(_ref?: RefObject<T | null> | null) => [RefObject<T>, boolean];
/**
* Hook that detects when a specific key is pressed
* @param targetKey - The key to detect (e.g., "Enter", "Escape", "a")
* @returns boolean indicating if the key is currently pressed
*/
declare function useKeyPress(targetKey: string): boolean;
type KeyCombo = string[];
/**
* Hook that detects when a specific combination of keys is pressed
* @param targetCombo - Array of keys that make up the combination (e.g., ["Control", "Shift", "a"])
* @returns boolean indicating if the combination is currently active
*/
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;
}
/**
* A hook that handles both normal press and long press interactions
*
* @param options - Configuration options for the long press behavior
* @param options.delay - Duration in milliseconds before a press is considered a long press (default: 400)
* @param options.preventContext - When true, prevents the default context menu on long press (default: true)
* @param options.onPress - Callback fired when a normal press (shorter than delay) is completed.
* Triggers only if the press duration was less than the specified delay
* @param options.onLongPress - Callback fired when a long press is successfully triggered.
* Triggers exactly once when the press duration exceeds the delay
* @param options.onLongPressCanceled - Callback fired when a long press is canceled before completion.
* Triggers if the press is released or canceled before reaching the delay threshold
* @returns Object containing event handlers and current press state
*
* @example
* ```tsx
* const MyComponent = () => {
* const { handlers, state } = useLongPress({
* delay: 1000,
* onPress: () => console.log('Normal press'),
* onLongPress: () => console.log('Long press completed'),
* onLongPressCanceled: () => console.log('Long press canceled'),
* });
*
* return (
* <button {...handlers}>
* {state.isLongPressed
* ? 'Long Press!'
* : `Hold me (${Math.round(state.progress * 100)}%)`}
* </button>
* );
* };
* ```
*/
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 window dimensions
* @returns Object containing current window width and height
* @example
* const { width, height } = useWindowSize();
* console.log(width, height);
*/
declare function useWindowSize(): WindowSize;
interface ScrollPosition {
x: number;
y: number;
}
/**
* Hook that tracks window scroll position
* @returns Object containing current scroll x and y coordinates
*/
declare function useScrollPosition(): ScrollPosition;
interface ContainerScroll {
scrollTop: number;
scrollLeft: number;
scrollWidth: number;
scrollHeight: number;
clientWidth: number;
clientHeight: number;
isScrolling: boolean;
}
/**
* Hook that tracks scroll position and dimensions of a container element
* @param containerRef - React ref object pointing to the container element
* @param delay - Delay in milliseconds before setting isScrolling to false
* @returns Object containing scroll position and dimension information
*/
declare function useContainerScroll(containerRef: RefObject<HTMLElement | null>, delay?: number): ContainerScroll;
interface ElementSize {
width: number;
height: number;
}
/**
* Hook that tracks an element's dimensions using ResizeObserver
* @param elementRef - React ref object pointing to the target element
* @returns Object containing current element width and height
*/
declare function useElementSize(elementRef: RefObject<HTMLElement>): ElementSize;
interface IntersectionOptions extends IntersectionObserverInit {
freezeOnceVisible?: boolean;
}
/**
* Hook that tracks element's intersection with viewport using IntersectionObserver
* @param elementRef - React ref object pointing to the target element
* @param options - IntersectionObserver options with additional freezeOnceVisible flag
* @returns IntersectionObserverEntry if available, null otherwise
*/
declare function useIntersectionObserver(elementRef: RefObject<HTMLElement>, { threshold, root, rootMargin, freezeOnceVisible, }?: IntersectionOptions): IntersectionObserverEntry | null;
interface ResizeObserverEntry {
contentRect: DOMRectReadOnly;
contentBoxSize: ReadonlyArray<ResizeObserverSize>;
borderBoxSize: ReadonlyArray<ResizeObserverSize>;
devicePixelContentBoxSize: ReadonlyArray<ResizeObserverSize>;
target: Element;
}
/**
* Hook that tracks element's size changes using ResizeObserver with full entry details
* @param elementRef - React ref object pointing to the target element
* @returns Latest ResizeObserverEntry if available, null otherwise
*/
declare function useResizeObserver(elementRef: RefObject<HTMLElement>): ResizeObserverEntry | null;
/**
* Hook that debounces a value
* @param value - The value to debounce
* @param delay - The delay in milliseconds
* @returns The debounced value
*/
declare function useDebounce<T>(value: T, delay: number): T;
/**
* Hook that throttles a value
* @param value - The value to throttle
* @param interval - The minimum time interval between updates in milliseconds
* @returns The throttled value
*/
declare function useThrottle<T>(value: T, interval: number): T;
interface TimerControls {
start: () => void;
pause: () => void;
reset: () => void;
isRunning: boolean;
}
/**
* Hook that provides a timer with controls
* @param initialTime - Initial time in seconds
* @param step - Time step in seconds (default: 1)
* @param countDown - Whether to count down instead of up (default: false)
* @returns [currentTime, controls]
*/
declare function useTimer(initialTime: number, step?: number, countDown?: boolean): [number, TimerControls];
/**
* Hook that sets up an interval that is properly cleaned up when the component unmounts
* @param callback - Function to call on each interval
* @param delay - Delay in milliseconds (null to pause)
*/
declare function useInterval(callback: () => void, delay: number | null): void;
type EventMap = WindowEventMap & HTMLElementEventMap & DocumentEventMap;
/**
* Hook that adds an event listener to a target element or window
* @param eventName - Name of the event to listen for
* @param handler - Event handler function
* @param element - Target element (defaults to window)
* @param options - AddEventListener options
*/
declare function useEventListener<K extends keyof EventMap>(eventName: K, handler: (event: EventMap[K]) => void, element?: RefObject<HTMLElement> | null, options?: boolean | AddEventListenerOptions): void;
/**
* 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 persisted in a browser cookie
* @param name - The name of the cookie
* @param initialValue - The initial value to use if no cookie exists
* @returns A tuple containing:
* - The current cookie value (or null if not set)
* - A function to update the cookie value and its options
* - A function to delete the cookie
*/
declare function useCookieState(name: string, initialValue: string): [string | null, (newValue: string, options?: CookieOptions) => void, () => void];
/**
* Hook for managing state persisted in localStorage
* @param key - The localStorage key
* @param initialValue - The initial value to use if no value exists in storage
* @returns A tuple containing the current value and a setter function
* @example
* const [value, setValue] = useLocalStorageState('my-key', 'initial value');
*/
declare function useLocalStorageState<T>(key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void];
/**
* Hook for managing state persisted in sessionStorage
* @param key - The sessionStorage key
* @param initialValue - The initial value to use if no value exists in storage
* @returns A tuple containing the current value and a setter function
* @example
* const [value, setValue] = useSessionStorageState('my-key', 'initial value');
*/
declare function useSessionStorageState<T>(key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void];
/**
* Hook for managing state persisted in URL query parameters
* @param key - The query parameter key
* @param initialValue - The initial value to use if the parameter doesn't exist
* @param options - Configuration options
* @param options.serialize - Function to convert value to string (default: JSON.stringify)
* @param options.deserialize - Function to parse string back to value (default: JSON.parse)
* @returns A tuple containing the current value and a setter function
*/
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, useDebounce, useElementSize, useEventListener, useHover, useIntersectionObserver, useInterval, useKeyCombo, useKeyPress, useLocalStorageState, useLongPress, useMediaQuery, useQueryParamsState, useResizeObserver, useScrollPosition, useSessionStorageState, useThrottle, useTimer, useWindowSize };