UNPKG

hookify-react

Version:

A collection of optimized and reusable React hooks for state management, dom interaction, responsive design, storage, location, asynchronous management and performance improvements.

764 lines (739 loc) 27.5 kB
import { EffectCallback, DependencyList, Dispatch, SetStateAction } from 'react'; /** * A custom React hook that enhances `useEffect` with advanced dependency comparison. * * - It **skips execution on the initial render**. * - It **executes the effect only if the dependencies actually change** between * renders (using `Object.is` per-dependency comparison). * - It prevents unnecessary re-executions when dependencies are referentially * different but shallow-equal. * * @param {EffectCallback} effect - The effect function to execute. * @param {DependencyList} deps - An array of dependencies that determine when the effect runs. * * @example * import { useAdvancedEffect } from "hookify-react"; * import { useState } from "react"; * * export default function UseAdvancedEffectExample() { * const [count, setCount] = useState(0); * * useAdvancedEffect(() => { * console.log("Effect triggered:", count); * }, [count]); * * return <button onClick={() => setCount((p) => p + 1)}>Increment</button>; * } */ declare function useAdvancedEffect(effect: EffectCallback, deps: DependencyList): void; /** * A custom hook that behaves like `useEffect` but skips the initial render, * running the effect only on subsequent dependency changes (updates). * * @param {EffectCallback} effect - The effect function to execute. * @param {DependencyList} deps - An array of dependencies to track changes. * * @example * import { useUpdatedEffect } from "hookify-react"; * * export default function Example() { * useUpdatedEffect(() => { * console.log("Effect triggered due to dependency change."); * }, [someState]); * * return <div>Check the console!</div>; * } */ declare function useUpdatedEffect(effect: EffectCallback, deps: DependencyList): void; type TUseArrayMethods<T> = { push: (value: T) => number; pop: () => T | undefined; shift: () => T | undefined; unshift: (value: T) => number; removeByIndex: (index: number) => void; removeByValue: (value: T) => void; clear: () => void; replace: (newArray: T[]) => void; reset: () => void; filter: (predicate: (value: T, index: number, array: T[]) => boolean) => void; updateByIndex: (index: number, value: T) => void; updateByValue: (prevValue: T, newValue: T) => void; }; type TUseArrayReturn<T> = readonly [ T[], Dispatch<SetStateAction<T[]>>, TUseArrayMethods<T> ]; /** * A custom React hook for managing arrays. It provides utility functions for * common operations like adding, removing, updating, and resetting elements. * * All helper functions are referentially stable (memoized), so they are safe to * pass to dependency arrays and memoized children. * * @template T - The type of elements in the array. * @param initialValue - The initial value of the array (defaults to an empty array). * @returns A tuple of `[state, setState, methods]` where `methods` includes: * `push`, `pop`, `shift`, `unshift`, `removeByIndex`, `removeByValue`, `clear`, * `replace`, `reset`, `filter`, `updateByIndex`, `updateByValue`. * * @example * import { useArray } from "hookify-react"; * * export default function UseArray() { * const [data, setData, { push, pop, clear }] = useArray<number>([1, 2, 3]); * return <button onClick={() => push(4)}>Add</button>; * } */ declare function useArray<T>(initialValue?: T[]): TUseArrayReturn<T>; type TUseCounterOptions = { /** Lower bound the counter cannot go below. */ min?: number; /** Upper bound the counter cannot exceed. */ max?: number; }; type TUseCounterReturn = { count: number; increment: () => void; incrementByValue: (value: number) => void; decrement: () => void; decrementByValue: (value: number) => void; set: (value: number) => void; reset: () => void; }; /** * A custom hook for managing a numeric counter with optional bounds. * * Supports incrementing, decrementing (by 1 or a specific amount), directly * setting a value, and resetting to the initial value. All returned functions * are referentially stable. Values are clamped to `min`/`max` when provided. * * @param initialValue - The initial value of the counter (defaults to 0). * @param options - Optional `{ min, max }` bounds. * @returns An object with `count`, `increment`, `incrementByValue`, * `decrement`, `decrementByValue`, `set`, and `reset`. * * @example * import { useCounter } from "hookify-react"; * * export default function UseCounter() { * const { count, increment, decrement } = useCounter(0, { min: 0 }); * return ( * <div> * <button onClick={decrement}>-1</button> * <span>{count}</span> * <button onClick={increment}>+1</button> * </div> * ); * } */ declare function useCounter(initialValue?: number, options?: TUseCounterOptions): TUseCounterReturn; type TError = string | undefined; type TDefaultValue<T> = T | (() => T); type TPredicates<T> = Array<(value: T) => TError>; type TOptions = { emptyInputValidation?: boolean; }; type TStatus = "idle" | "valid" | "error"; type TData = { errors: Array<string>; isValid: boolean; status: TStatus; }; type TUseFormStateReturn<T> = readonly [T, Dispatch<SetStateAction<T>>, TData]; /** * Custom hook to manage a single form field with validation and error tracking. * * @template T - Type of the form state value. * @param {TDefaultValue<T>} defaultValue - Initial value or a function returning it. * @param {TPredicates<T>} predicates - Validation functions, each returning an error message or `undefined`. * @param {TOptions} [options] - `{ emptyInputValidation }`; when `false`, an empty string clears errors. * @returns A tuple `[value, setValue, { errors, isValid, status }]`. * * @example * import { useFormState } from "hookify-react"; * * export default function UseFormState() { * const [name, setName, { errors, isValid }] = useFormState("", [ * (v) => (v.length < 3 ? "Name must have at least 3 characters" : undefined), * ]); * * return <input value={name} onChange={(e) => setName(e.target.value)} />; * } */ declare function useFormState<T>(defaultValue: TDefaultValue<T>, predicates: TPredicates<T>, { emptyInputValidation }?: TOptions): TUseFormStateReturn<T>; type TUseHistoryOptions = { capacity?: number; }; type TUseHistoryReturn<T> = readonly [ T, (value: T | ((prev: T) => T)) => void, { history: T[]; pointer: number; back: () => void; forward: () => void; go: (index: number) => void; } ]; /** * Custom hook to manage state with history tracking, supporting undo/redo functionality. * * @template T - The type of the state value. * @param {T | (() => T)} defaultValue - Initial state value or a function returning the initial value. * @param {TUseHistoryOptions} [options] - Configuration options for history tracking. * @returns {[T, (value: T | ((prev: T) => T)) => void, { history: T[], pointer: number, back: () => void, forward: () => void, go: (index: number) => void }]} * Returns the current state, a setter function, and an object containing history, pointer, and navigation functions. * * @example * import { useHistory } from "hookify-react"; * * export default function UseHistory() { * const [value, setValue, { history, pointer, forward, go, back }] = useHistory([0]); * * return <div>Play with the value</div>; * } */ declare function useHistory<T>(defaultValue: T | (() => T), { capacity }?: TUseHistoryOptions): TUseHistoryReturn<T>; /** * Custom React hook to store and retrieve the previous value of a given state or prop. * * @template T - The type of the tracked value. * @param value - The current value to track. * @returns The previous value before the last update, or `null` if no previous value exists. * * @example * import { useState } from "react"; * import { usePrevious } from "hookify-react"; * * export default function UsePreviousExample() { * const [count, setCount] = useState(0); * const prevCount = usePrevious(count); * * console.log(`Previous count: ${prevCount}, Current count: ${count}`); * * return ( * <div> * <button onClick={() => setCount(prev => prev + 1)}>+1</button> * <p>Current count value: <strong>{count}</strong></p> * <p>Previous count value: <strong>{prevCount}</strong></p> * </div> * ); * } */ declare function usePrevious<T>(value: T): T | null; type TUseToggleReturn = readonly [boolean, (value?: boolean) => void]; /** * A custom React hook that manages a boolean state and provides a function to * toggle it, or force it to a specific value. * * Calling the setter with no argument flips the value; passing a boolean sets it * explicitly. The setter has a stable identity across renders. * * @param initialValue - The initial boolean value (defaults to `false`). * @returns A tuple of `[state, toggle]`. * * @example * import { useToggle } from 'hookify-react'; * * export default function UseToggleExample() { * const [isToggled, toggle] = useToggle(false); * * return ( * <div> * <button onClick={() => toggle()}>Toggle</button> * <button onClick={() => toggle(true)}>On</button> * <button onClick={() => toggle(false)}>Off</button> * <p>{isToggled ? 'On' : 'Off'}</p> * </div> * ); * } */ declare function useToggle(initialValue?: boolean): TUseToggleReturn; /** * Custom hook that delays the execution of a callback function * until after a specified delay has elapsed since the last change in dependencies. * * Each time the dependencies change the pending timer is reset, so the callback * only fires once the dependencies have been stable for `delay` milliseconds. * The timer is cleared automatically on unmount. The latest `callback` is always * used, so it does not need to be memoized. * * @param {() => void} callback - The function to debounce. * @param {number} delay - The delay in milliseconds before executing the callback. * @param {unknown[]} deps - The dependencies that trigger the debounce effect. * * @example * // Usage example in a component: * function SearchComponent() { * const [query, setQuery] = useState(""); * * useDebounce(() => { * console.log("Searching for:", query); * }, 500, [query]); * * return ( * <input * type="text" * placeholder="Search..." * value={query} * onChange={(e) => setQuery(e.target.value)} * /> * ); * } */ declare function useDebounce(callback: () => void, delay: number, deps: unknown[]): void; type TUseTimeoutReturn = { set: () => void; clear: () => void; reset: () => void; }; /** * Custom hook for managing a timeout. * * The timeout starts automatically on mount and is cleared automatically on * unmount. The latest `callback` is always used without restarting the timer, * so you never need to memoize it. * * @param {() => void} callback - The function to execute when the timeout completes. * @param {number} delay - The delay in milliseconds for the timeout. * @returns {{ set: () => void, clear: () => void, reset: () => void }} - Functions to control the timeout. * * @example * // Example usage in a component: * function ExampleComponent() { * const { set, clear, reset } = useTimeout(() => { * console.log("Timeout executed!"); * }, 1000); * * return ( * <div> * <button onClick={set}>Start Timeout</button> * <button onClick={clear}>Clear Timeout</button> * <button onClick={reset}>Reset Timeout</button> * </div> * ); * } */ declare function useTimeout(callback: () => void, delay: number): TUseTimeoutReturn; type TUseIntervalReturn = { clear: () => void; }; /** * A custom hook to execute a callback at a specified interval. * * The latest `callback` is always used without restarting the timer, so it does * not need to be memoized. Passing `null` as the interval pauses execution, and * the interval is cleared automatically on unmount. * * @param {() => void} callback - The function to execute at each interval. * @param {number | null} [interval=1000] - The time in milliseconds between executions, or `null` to pause (defaults to 1000ms). * @returns {{ clear: () => void }} - A function to stop the interval. * * @example * import { useState } from "react"; * import { useInterval } from "hookify-react"; * * export default function UseIntervalExample() { * const [count, setCount] = useState(0); * const { clear } = useInterval(() => setCount((prev) => prev + 1), 1000); * * return ( * <div> * <p>Counter: {count}</p> * <button onClick={clear}>Stop Timer</button> * </div> * ); * } */ declare function useInterval(callback: () => void, interval?: number | null): TUseIntervalReturn; type TUseStorageReturn<T> = readonly [T, Dispatch<SetStateAction<T>>]; /** * Core hook to synchronize React state with Web Storage. * * - Safe during server-side rendering (falls back to the default value). * - Persists writes to storage and removes the key when the value is `undefined`. * - Synchronizes across browser tabs/windows via the `storage` event. * * @template T - The type of the stored value. * @param key - The storage key. * @param defaultValue - The default value or a function returning it. * @param type - Which storage area to use: `"local"` or `"session"`. * @returns A tuple `[value, setValue]`. */ declare function useStorage<T>(key: string, defaultValue: T | (() => T), type: "local" | "session"): TUseStorageReturn<T>; /** * Hook for syncing state with `localStorage` (with cross-tab synchronization). * * @template T - The type of the stored value. * @param key - The storage key. * @param defaultValue - The default value or a function returning it. * * @example * import { useLocalStorage } from "hookify-react"; * * export default function UseLocalStorage() { * const [name, setName] = useLocalStorage("name", "default name"); * return <input value={name} onChange={(e) => setName(e.target.value)} />; * } */ declare function useLocalStorage<T>(key: string, defaultValue: T | (() => T)): TUseStorageReturn<T>; /** * Hook for syncing state with `sessionStorage`. * * @template T - The type of the stored value. * @param key - The storage key. * @param defaultValue - The default value or a function returning it. * * @example * import { useSessionStorage } from "hookify-react"; * * export default function UseSessionStorage() { * const [name, setName] = useSessionStorage("name", "default name"); * return <input value={name} onChange={(e) => setName(e.target.value)} />; * } */ declare function useSessionStorage<T>(key: string, defaultValue: T | (() => T)): TUseStorageReturn<T>; type TUseCopyToClipboardReturn = { copy: (text: string) => Promise<boolean>; isCopied: boolean; error: Error | null; }; /** * A custom React hook to copy text to the clipboard. * * - Uses the modern `navigator.clipboard` API when available, with a * `document.execCommand` fallback for insecure (non-HTTPS) contexts. * - Reports success/failure and exposes the thrown `Error` (not just a string). * - Automatically resets `isCopied` after `resetDelay` and clears the timer on * unmount to avoid state updates on an unmounted component. * * @param {number} [resetDelay=2000] - Milliseconds before `isCopied` resets to `false`. Pass `0` to disable auto-reset. * @returns {Object} An object containing: * - `copy`: An async function that copies text and resolves to `true` on success. * - `isCopied`: Boolean indicating whether the last copy succeeded. * - `error`: The `Error` thrown by the last failed copy, or `null`. * * @example * import { useCopyToClipboard } from "hookify-react"; * * export default function UseCopyToClipboard() { * const { copy, isCopied, error } = useCopyToClipboard(); * * return ( * <div> * <button onClick={() => copy("Hello, Clipboard!")}>Copy</button> * {isCopied && <span>Copied successfully!</span>} * {error && <span>Error copying: {error.message}</span>} * </div> * ); * } */ declare function useCopyToClipboard(resetDelay?: number): TUseCopyToClipboardReturn; /** Listen to a `window` event (the default target). */ declare function useEventListener<K extends keyof WindowEventMap>(eventType: K, callback: (event: WindowEventMap[K]) => void, elementRef?: React.RefObject<Window | null>, options?: boolean | AddEventListenerOptions): void; /** Listen to a `document` event. */ declare function useEventListener<K extends keyof DocumentEventMap>(eventType: K, callback: (event: DocumentEventMap[K]) => void, elementRef: React.RefObject<Document | null>, options?: boolean | AddEventListenerOptions): void; /** Listen to an `HTMLElement` event. */ declare function useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement>(eventType: K, callback: (event: HTMLElementEventMap[K]) => void, elementRef: React.RefObject<T | null>, options?: boolean | AddEventListenerOptions): void; type TUseHoverReturn<T> = { ref: React.MutableRefObject<T | null>; isHovered: boolean; }; /** * A custom hook to track hover state on an element. * * @returns {Object} An object containing: * - `ref`: A ref that you can attach to any element * - `isHovered`: Boolean indicating an element is being hovered or not * * @example * import { useHover } from "hookify-react"; * * export default function UseHoverExample() { * const { ref, isHovered } = useHover(); * * return ( * <div * ref={ref} * style={{ * width: "200px", * height: "100px", * display: "flex", * alignItems: "center", * justifyContent: "center", * background: isHovered ? "blue" : "gray", * color: "white", * fontSize: "18px", * borderRadius: "8px", * transition: "background 0.3s ease", * }} * > * {isHovered ? "Hovered! 🎯" : "Hover over me!"} * </div> * ); * } */ declare function useHover<T extends HTMLElement>(): TUseHoverReturn<T>; type TUseClickOutsideReturn<T> = { ref: React.MutableRefObject<T | null>; }; /** * A custom React hook that listens for clicks (and touches) outside of a * specified element and triggers a callback. * * Attach the returned `ref` to the element you want to watch. The listener is * registered on `document`, is safe during server-side rendering, and always * calls the latest `callback`. * * @param callback - A function executed when a click/touch occurs outside the element. * @returns {Object} An object containing: * - `ref`: A ref that you attach to the element to watch. * * @example * import { useClickOutside } from "hookify-react"; * import { useState } from "react"; * * export default function ClickOutsideExample() { * const [isOpen, setIsOpen] = useState(true); * const { ref } = useClickOutside<HTMLDivElement>(() => setIsOpen(false)); * * return ( * <div> * <button onClick={() => setIsOpen(true)}>Open Modal</button> * {isOpen && ( * <div ref={ref}>Click outside of this box to close it.</div> * )} * </div> * ); * } */ declare function useClickOutside<T extends HTMLElement>(callback: (event: MouseEvent | TouchEvent) => void): TUseClickOutsideReturn<T>; type TOnlineStatus = "online" | "offline"; type TUseOnlineStatusReturn = { isOnline: boolean; onlineStatus: TOnlineStatus; }; /** * A custom hook that tracks the online/offline status of the user's network connection. * * Listens to both the `online` and `offline` window events and is safe to call * during server-side rendering (it assumes "online" on the server). * * @returns {Object} An object containing: * - `isOnline`: Boolean, `true` when the browser reports a connection. * - `onlineStatus`: Either `"online"` or `"offline"`. * * @example * import { useOnlineStatus } from "hookify-react"; * * export default function UseOnlineStatusExample() { * const { onlineStatus } = useOnlineStatus(); * * return ( * <div> * {onlineStatus === "online" ? "You are online 😁" : "You are offline 😥"} * </div> * ); * } */ declare function useOnlineStatus(): TUseOnlineStatusReturn; type TUseOnScreenOptions = { /** Margin around the root, like CSS margin (e.g. `"-100px"`). */ rootMargin?: string; /** Intersection ratio(s) at which the callback fires. */ threshold?: number | number[]; /** Once visible, stay visible and stop observing. */ once?: boolean; }; type TUseOnScreenReturn<T> = { ref: React.MutableRefObject<T | null>; isVisible: boolean; }; /** * Custom hook to check whether an element is visible within the viewport, using * `IntersectionObserver`. * * Safe during server-side rendering and in browsers without * `IntersectionObserver` (it simply reports `false` instead of throwing). * * @param options - Either a `rootMargin` string (for backwards compatibility) or an options object. * @returns {Object} An object containing: * - `ref`: A ref to attach to the target element. * - `isVisible`: `true` while the element intersects the viewport. * * @example * import { useOnScreen } from "hookify-react"; * * export default function UseOnScreenExample() { * const { ref, isVisible } = useOnScreen("-100px"); * * return ( * <div ref={ref}> * {isVisible ? "I'm visible! 🎉" : "Not in view 👀"} * </div> * ); * } */ declare function useOnScreen<T extends HTMLElement>(options?: string | TUseOnScreenOptions): TUseOnScreenReturn<T>; type TUsePressReturn<T> = { ref: React.MutableRefObject<T | null>; isPressed: boolean; }; /** * Custom hook to track whether an element is currently being pressed. * * Supports both mouse and touch input. The press is released even if the * pointer is lifted outside the element (the `mouseup`/`touchend` listeners are * on `window`) or leaves the element while held. * * @returns {Object} An object containing: * - `ref`: A ref to attach to the target element. * - `isPressed`: Boolean indicating whether the element is being pressed. * * @example * import { usePress } from "hookify-react"; * * export default function UsePressExample() { * const { ref, isPressed } = usePress<HTMLButtonElement>(); * * return ( * <button ref={ref}> * {isPressed ? "Wow that feels good! 😁" : "Please press me! 😥"} * </button> * ); * } */ declare function usePress<T extends HTMLElement>(): TUsePressReturn<T>; type ScrollDirection = "up" | "down" | "left" | "right" | "none"; type ScrollInfo = { scrollX: number; scrollY: number; scrollDirection: ScrollDirection; isScrolling: boolean; scrollProgress: number; }; type TUseScrollInfoReturn<T> = { ref: React.MutableRefObject<T | null>; } & ScrollInfo; /** * Custom hook to track scroll position, direction, and activity. * * Tracks the element attached to the returned `ref`, or the window when no ref * is attached. The `scroll` listener is passive (it never blocks scrolling) and * all timers/listeners are cleaned up on unmount. * * @returns {Object} An object containing: * - `ref`: A ref to attach to a scrollable element (optional; defaults to the window). * - `scrollX` / `scrollY`: Current scroll offsets. * - `scrollDirection`: `"up" | "down" | "left" | "right" | "none"`. * - `isScrolling`: `true` while scrolling is in progress. * - `scrollProgress`: Vertical scroll completion as a percentage (0-100). * * @example * import { useScrollInfo } from "hookify-react"; * * export default function UseScrollInfo() { * const { ref, scrollY, scrollDirection, scrollProgress } = * useScrollInfo<HTMLDivElement>(); * * return <div ref={ref}>Scroll me</div>; * } */ declare function useScrollInfo<T extends HTMLElement>(): TUseScrollInfoReturn<T>; type TSize = { width: number; height: number; top: number; left: number; bottom: number; right: number; }; type TUseSizeReturn<T> = { ref: React.MutableRefObject<T | null>; size: TSize | null; }; /** * Custom hook to track the size of an HTML element in real-time. * * @template T - The HTMLElement type. * @returns {Object} An object containing: * - `ref`: A ref to attach to the target element. * - `size`: The element's current size (`width`, `height`, `top`, `left`, `bottom`, `right`). * * @example * import { useSize } from "hookify-react"; * * export default function UseSize() { * const { ref, size } = useSize<HTMLDivElement>(); * * return <div ref={ref}>Get size of this element</div> * } * ``` */ declare function useSize<T extends HTMLElement>(): TUseSizeReturn<T>; type WindowSize = { width: number; height: number; }; type TUseWindowSizeReturn = WindowSize; /** * Custom hook to track the size of the browser window in real-time. * * @returns {Object} An object containing: * - `width`: The current width of the window. * - `height`: The current height of the window. * * @example * import { useWindowSize } from "hookify-react"; * * export default function UseWindowSize() { * const { width, height } = useWindowSize(); * console.log(`Window Size: ${width} x ${height}`); * * return <div>Get a window size</div> * } */ declare function useWindowSize(): TUseWindowSizeReturn; type TPositionError = { code: number; message: string; }; type TUseLocationReturn = { loading: boolean; error: TPositionError | null; coords: GeolocationCoordinates | null; }; type GeoLocationOptions = { enableHighAccuracy?: boolean; maximumAge?: number; timeout?: number; retryLimit?: number; retryDelay?: number; }; /** * Custom hook to track the user's geolocation with retry support. * * Options are consumed by their primitive values (not by object identity), so * passing a fresh inline `options` object on every render will **not** cause an * infinite re-subscription loop. The watcher and any pending retry timer are * cleaned up on unmount. * * @param options - Configuration for geolocation tracking. * @returns {Object} An object containing: * - `loading`: Whether location data is currently being fetched. * - `error`: Error details if location retrieval fails, otherwise `null`. * - `coords`: The latest coordinates, or `null` before the first fix. * * @example * import { useGeoLocation } from "hookify-react"; * * export default function UseGeoLocation() { * const { loading, error, coords } = useGeoLocation({ enableHighAccuracy: true }); * if (loading) return <p>Fetching location...</p>; * if (error) return <p>Error: {error.message}</p>; * return <p>Lat: {coords?.latitude}, Lng: {coords?.longitude}</p>; * } */ declare function useGeoLocation(options?: GeoLocationOptions): TUseLocationReturn; export { type TUseArrayMethods, useAdvancedEffect, useArray, useClickOutside, useCopyToClipboard, useCounter, useDebounce, useEventListener, useFormState, useGeoLocation, useHistory, useHover, useInterval, useLocalStorage, useOnScreen, useOnlineStatus, usePress, usePrevious, useScrollInfo, useSessionStorage, useSize, useStorage, useTimeout, useToggle, useUpdatedEffect, useWindowSize };