@qazuor/react-hooks
Version:
A comprehensive collection of production-ready React hooks for modern web applications. Features type-safe implementations, extensive testing, and zero dependencies. Includes hooks for state management, browser APIs, user interactions, and development uti
994 lines (974 loc) • 29.5 kB
JavaScript
// src/hooks/useBoolean.ts
import { useCallback, useState } from "react";
function useBoolean(initialValue = false) {
const [value, setValue] = useState(initialValue);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
const toggle = useCallback(() => setValue((v) => !v), []);
const setValueDirect = useCallback((newValue) => setValue(newValue), []);
return { value, setTrue, setFalse, toggle, setValue: setValueDirect };
}
// src/hooks/useClickOutside.ts
import { useCallback as useCallback2, useEffect } from "react";
function useClickOutside(ref, handler, { enabled = true, eventType = "mousedown" } = {}) {
const handleClickOutside = useCallback2(
(event) => {
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
},
[ref, handler]
);
useEffect(() => {
if (!enabled) {
return;
}
document.addEventListener(eventType, handleClickOutside);
return () => {
document.removeEventListener(eventType, handleClickOutside);
};
}, [ref, handler, enabled, eventType, handleClickOutside]);
}
// src/hooks/useCopyToClipboard.ts
import { useCallback as useCallback3, useEffect as useEffect2, useRef, useState as useState2 } from "react";
function useCopyToClipboard() {
const [state, setState] = useState2({
copied: false,
error: null
});
const timeoutRef = useRef(null);
const copy = useCallback3(async (text) => {
try {
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
}
await navigator.clipboard.writeText(text);
setState({ copied: true, error: null });
timeoutRef.current = window.setTimeout(() => {
setState((prev) => ({ ...prev, copied: false }));
}, 2e3);
} catch (err) {
setState({ copied: false, error: err });
}
}, []);
const reset = useCallback3(() => {
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
}
setState({ copied: false, error: null });
}, []);
useEffect2(() => {
return () => {
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
}
};
}, []);
return { copy, reset, ...state };
}
// src/hooks/useDebounce.ts
import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
function useDebounce(value, delay, immediate = false) {
const [debouncedValue, setDebouncedValue] = useState3(value);
const timeoutRef = useRef2(null);
useEffect3(() => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
if (immediate) {
setDebouncedValue(value);
return;
}
timeoutRef.current = window.setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, [value, delay, immediate]);
return debouncedValue;
}
// src/hooks/useHandledInterval.ts
import { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
function useHandledInterval({
callback,
delay,
random = false,
autoStart = true,
minDelay = 0
}) {
const callbackRef = useRef3(callback);
const [currentDelay, setCurrentDelay] = useState4(delay);
const [isRunning, setIsRunning] = useState4(false);
const intervalId = useRef3(null);
const nextIntervalDelay = useRef3(null);
const getRandomDelay = useCallback4(() => {
if (!random) {
return currentDelay;
}
const range = currentDelay - minDelay;
return Math.floor(Math.random() * range) + minDelay;
}, [currentDelay, random, minDelay]);
const clear = useCallback4(() => {
if (intervalId.current)
clearInterval(intervalId.current);
intervalId.current = null;
nextIntervalDelay.current = null;
}, []);
const start = useCallback4(() => {
if (intervalId.current)
return;
setIsRunning(true);
nextIntervalDelay.current = getRandomDelay();
intervalId.current = window.setInterval(() => {
callbackRef.current();
if (random) {
clear();
nextIntervalDelay.current = getRandomDelay();
intervalId.current = window.setInterval(callbackRef.current, nextIntervalDelay.current);
}
}, nextIntervalDelay.current);
}, [random, getRandomDelay, clear]);
const pause = useCallback4(() => {
setIsRunning(false);
clear();
}, [clear]);
const reset = useCallback4(() => {
pause();
start();
}, [pause, start]);
const setDelay = useCallback4(
(newDelay) => {
setCurrentDelay(newDelay);
if (isRunning) {
clear();
start();
}
},
[isRunning, clear, start]
);
useEffect4(() => {
callbackRef.current = callback;
}, [callback]);
useEffect4(() => {
if (autoStart) {
start();
}
return () => clear();
}, [autoStart, start, clear]);
return { isRunning, start, pause, reset, setDelay };
}
// src/hooks/useIdleness.ts
import { useCallback as useCallback5, useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
function useIdleness({
timeout,
events = ["mousemove", "keydown", "wheel", "touchstart"],
startImmediately = true,
onIdleChange
}) {
const [idle, setIdle] = useState5(false);
const [isMonitoring, setIsMonitoring] = useState5(startImmediately);
const timerId = useRef4(null);
const mounted = useRef4(true);
const eventsRef = useRef4(events);
const clearTimer = useCallback5(() => {
if (timerId.current) {
clearTimeout(timerId.current);
timerId.current = null;
}
}, []);
const setIdleState = useCallback5(
(newState) => {
if (mounted.current && idle !== newState) {
setIdle(newState);
onIdleChange?.(newState);
}
},
[idle, onIdleChange]
);
const resetTimer = useCallback5(() => {
clearTimer();
setIdleState(false);
if (isMonitoring) {
timerId.current = setTimeout(() => {
setIdleState(true);
}, timeout);
}
}, [timeout, isMonitoring, clearTimer, setIdleState]);
const start = useCallback5(() => {
setIsMonitoring(true);
resetTimer();
resetTimer();
}, [resetTimer]);
const stop = useCallback5(() => {
setIsMonitoring(false);
clearTimer();
setIdleState(false);
}, [clearTimer, setIdleState]);
useEffect5(() => {
if (isMonitoring) {
eventsRef.current.forEach((evt) => window.addEventListener(evt, resetTimer));
timerId.current = setTimeout(() => {
setIdleState(true);
}, timeout);
}
return () => {
clearTimer();
eventsRef.current.forEach((evt) => window.removeEventListener(evt, resetTimer));
};
}, [isMonitoring, resetTimer, clearTimer, timeout, setIdleState]);
useEffect5(
() => () => {
mounted.current = false;
},
[]
);
return { isIdle: idle, start, stop, reset: resetTimer };
}
// src/hooks/useInterval.ts
import { useCallback as useCallback6, useEffect as useEffect6, useRef as useRef5, useState as useState6 } from "react";
function useInterval({
callback,
delay,
runImmediately = false,
autoStart = true
}) {
const savedCallback = useRef5(callback);
const [isRunning, setIsRunning] = useState6(false);
const intervalId = useRef5(null);
useEffect6(() => {
savedCallback.current = callback;
}, [callback]);
const cleanup = useCallback6(() => {
if (intervalId.current !== null) {
clearInterval(intervalId.current);
intervalId.current = null;
}
}, []);
const start = useCallback6(() => {
if (delay === null) {
setIsRunning(false);
return;
}
if (intervalId.current !== null) {
return;
}
setIsRunning(true);
if (runImmediately) {
savedCallback.current();
}
intervalId.current = window.setInterval(() => {
savedCallback.current();
}, delay);
}, [delay, runImmediately]);
const pause = useCallback6(() => {
cleanup();
setIsRunning(false);
}, [cleanup]);
const restart = useCallback6(() => {
cleanup();
start();
}, [cleanup, start]);
useEffect6(() => {
if (autoStart) {
start();
}
return cleanup;
}, [autoStart, start, cleanup]);
return { isRunning, start, pause, restart };
}
// src/hooks/useLocalStorage.ts
import { useCallback as useCallback7, useEffect as useEffect7, useRef as useRef6, useState as useState7 } from "react";
function useLocalStorage(key, initialValue, options = {}) {
const {
serializer = JSON.stringify,
deserializer = JSON.parse,
onError = console.error,
syncTabs = false
} = options;
const mounted = useRef6(true);
const [storedValue, setStoredValue] = useState7(() => {
try {
const item = window.localStorage.getItem(key);
if (item) {
return deserializer(item);
}
window.localStorage.setItem(key, serializer(initialValue));
return initialValue;
} catch (error) {
onError(error);
return initialValue;
}
});
const setValue = useCallback7(
(value) => {
setStoredValue((prev) => {
try {
const newValue = value instanceof Function ? value(prev) : value;
window.localStorage.setItem(key, serializer(newValue));
return newValue;
} catch (error) {
onError(error);
return prev;
}
});
},
[key, serializer, onError]
);
const handleStorageChange = useCallback7(
(event) => {
if (event.key === key && event.newValue !== null && mounted.current) {
try {
const newValue = deserializer(event.newValue);
setStoredValue(newValue);
} catch (error) {
onError(error);
}
}
},
[key, deserializer, onError]
);
useEffect7(() => {
if (syncTabs) {
window.addEventListener("storage", handleStorageChange);
return () => {
window.removeEventListener("storage", handleStorageChange);
};
}
}, [syncTabs, handleStorageChange]);
useEffect7(
() => () => {
mounted.current = false;
},
[]
);
return [storedValue, setValue];
}
// src/hooks/useLockBodyScroll.ts
import { useCallback as useCallback8, useEffect as useEffect8, useState as useState8 } from "react";
var isTestEnv = () => true;
function useLockBodyScroll({
preservePosition = true,
lockImmediately = true,
additionalStyles = {}
} = {}) {
const [isLocked, setIsLocked] = useState8(lockImmediately);
const [originalStyles, setOriginalStyles] = useState8({});
const [scrollPosition, setScrollPosition] = useState8(0);
const applyStyles = useCallback8((styles) => {
Object.entries(styles).forEach(([key, value]) => {
document.body.style[key] = value?.toString() ?? "";
});
}, []);
const saveScrollPosition = useCallback8(() => {
if (preservePosition) {
setScrollPosition(window.pageYOffset);
}
}, [preservePosition]);
const restoreScrollPosition = useCallback8(() => {
if (preservePosition) {
try {
if (!isTestEnv()) {
window.scrollTo(0, scrollPosition);
}
} catch (error) {
if (!isTestEnv()) {
console.error(error);
}
}
}
}, [preservePosition, scrollPosition]);
const lock = useCallback8(() => {
if (!isLocked) {
saveScrollPosition();
const originalStyle = {};
["overflow", "position", "top", "width"].forEach((prop) => {
originalStyle[prop] = document.body.style[prop];
});
Object.keys(additionalStyles).forEach((key) => {
originalStyle[key] = document.body.style[key];
});
setOriginalStyles(originalStyle);
const lockStyles = {
overflow: "hidden",
position: "fixed",
top: `-${scrollPosition}px`,
width: "100%",
...additionalStyles
};
applyStyles(lockStyles);
setIsLocked(true);
}
}, [isLocked, scrollPosition, additionalStyles, saveScrollPosition, applyStyles]);
const unlock = useCallback8(() => {
if (isLocked) {
applyStyles(originalStyles);
restoreScrollPosition();
setIsLocked(false);
}
}, [isLocked, originalStyles, restoreScrollPosition, applyStyles]);
const toggle = useCallback8(() => {
if (isLocked) {
unlock();
} else {
lock();
}
}, [isLocked, lock, unlock]);
useEffect8(() => {
if (lockImmediately) {
lock();
if (isTestEnv()) {
document.body.style.overflow = "hidden";
document.body.style.position = "fixed";
document.body.style.top = scrollPosition > 0 ? `-${scrollPosition}px` : "-0px";
document.body.style.width = "100%";
Object.entries(additionalStyles).forEach(([key, value]) => {
document.body.style[key] = value;
});
}
}
return unlock;
}, [lockImmediately, lock, unlock, scrollPosition, additionalStyles]);
return { isLocked, lock, unlock, toggle };
}
// src/hooks/useLogger.ts
import { useCallback as useCallback9, useEffect as useEffect9, useRef as useRef7 } from "react";
function useLogger(label, value, options = {}) {
const { level = "info", timestamp = true, enabled = true, formatter } = options;
const prevValue = useRef7(null);
const isFirstRender = useRef7(true);
const getTimestamp = useCallback9(() => {
if (!timestamp)
return "";
return `[${(/* @__PURE__ */ new Date()).toISOString()}] `;
}, [timestamp]);
const formatMessage = useCallback9(
(msg) => {
if (formatter) {
return formatter(label, msg);
}
return `${getTimestamp()}[${label}] ${JSON.stringify(msg)}`;
},
[label, getTimestamp, formatter]
);
const log = useCallback9(() => {
if (!enabled)
return;
const message = formatMessage(value);
switch (level) {
case "warn":
console.warn(message);
break;
case "error":
console.error(message);
break;
case "debug":
console.debug(message);
break;
default:
console.info(message);
}
}, [enabled, level, value, formatMessage]);
useEffect9(() => {
if (isFirstRender.current || value !== prevValue.current) {
log();
isFirstRender.current = false;
prevValue.current = value;
}
}, [value, log]);
return log;
}
// src/hooks/useMeasure.ts
import { useCallback as useCallback10, useEffect as useEffect10, useRef as useRef8, useState as useState9 } from "react";
function useMeasure() {
const [size, setSize] = useState9({ width: 0, height: 0 });
const observerRef = useRef8({ observer: null, element: null });
const ref = useCallback10((node) => {
if (!node)
return;
if (observerRef.current.observer) {
observerRef.current.observer.disconnect();
}
const observer = new ResizeObserver(([entry]) => {
setSize({
width: entry.contentRect.width,
height: entry.contentRect.height
});
});
observerRef.current = { observer, element: node };
observer.observe(node);
return () => observer.disconnect();
}, []);
useEffect10(() => {
return () => {
if (observerRef.current.observer) {
observerRef.current.observer.disconnect();
}
};
}, []);
return { ref, size };
}
// src/hooks/useMediaQuery.ts
import { useCallback as useCallback11, useEffect as useEffect11, useState as useState10 } from "react";
function useMediaQuery(query, { ssrSafe = true, ssrDefaultValue = false, watchImmediately = true } = {}) {
const [shouldListen, setShouldListen] = useState10(watchImmediately);
const [matches, setMatches] = useState10(() => {
if (ssrSafe && typeof window === "undefined") {
return ssrDefaultValue;
}
if (typeof window !== "undefined" && window.matchMedia) {
try {
return window.matchMedia(query).matches;
} catch (e) {
console.error("Failed to get initial media query match:", query, e);
return ssrDefaultValue;
}
}
return ssrDefaultValue;
});
const handleChange = useCallback11((e) => {
setMatches(e.matches);
}, []);
const startWatching = useCallback11(() => {
if (typeof window !== "undefined" && window.matchMedia !== void 0) {
setShouldListen(true);
} else {
console.warn("Cannot start watching media query: window.matchMedia is not available.");
}
}, []);
const stopWatching = useCallback11(() => {
setShouldListen(false);
}, []);
useEffect11(() => {
let mediaQueryList = null;
let handler = null;
if (shouldListen && typeof window !== "undefined" && window.matchMedia !== void 0) {
try {
mediaQueryList = window.matchMedia(query);
setMatches(mediaQueryList.matches);
handler = handleChange;
if (mediaQueryList.addEventListener) {
mediaQueryList.addEventListener("change", handler);
} else {
mediaQueryList.addListener(handler);
}
} catch (e) {
console.error("Failed to set up media query listener:", query, e);
setShouldListen(false);
}
}
return () => {
if (mediaQueryList && handler) {
try {
if (mediaQueryList.removeEventListener) {
mediaQueryList.removeEventListener("change", handler);
} else {
mediaQueryList.removeListener(handler);
}
} catch (e) {
console.error("Failed to remove media query listener during cleanup:", query, e);
}
}
};
}, [query, shouldListen, handleChange]);
useEffect11(() => {
if (ssrSafe && typeof window !== "undefined" && window.matchMedia !== void 0) {
try {
setMatches(window.matchMedia(query).matches);
} catch (e) {
console.error("Failed to update matches state after hydration:", query, e);
}
}
}, [query, ssrSafe]);
return { matches, startWatching, stopWatching };
}
// src/hooks/useNetworkState.ts
import { useCallback as useCallback12, useEffect as useEffect12, useState as useState11 } from "react";
function useNetworkState() {
const [state, setState] = useState11(() => ({
online: navigator.onLine,
...navigator.connection || {}
}));
const updateNetworkInfo = useCallback12(() => {
const connection = navigator.connection;
setState({
online: navigator.onLine,
downlink: connection?.downlink,
downlinkMax: connection?.downlinkMax,
effectiveType: connection?.effectiveType,
rtt: connection?.rtt,
saveData: connection?.saveData,
type: connection?.type
});
}, []);
useEffect12(() => {
const connection = navigator.connection;
window.addEventListener("online", updateNetworkInfo);
window.addEventListener("offline", updateNetworkInfo);
if (connection) {
connection?.addEventListener?.("change", updateNetworkInfo);
}
return () => {
window.removeEventListener("online", updateNetworkInfo);
window.removeEventListener("offline", updateNetworkInfo);
if (connection) {
connection?.removeEventListener?.("change", updateNetworkInfo);
}
};
}, [updateNetworkInfo]);
return { ...state, checkConnection: updateNetworkInfo };
}
// src/hooks/usePageLeave.ts
import { useCallback as useCallback13, useEffect as useEffect13, useState as useState12 } from "react";
function usePageLeave(options = {}) {
const { threshold = 0, enabled = true, onLeave, onReturn } = options;
const [hasLeft, setHasLeft] = useState12(false);
const [isEnabled, setIsEnabled] = useState12(enabled);
const handleMouseOut = useCallback13(
(e) => {
if (!isEnabled)
return;
if (e.clientY <= threshold) {
setHasLeft(true);
onLeave?.();
}
},
[threshold, isEnabled, onLeave]
);
const handleMouseOver = useCallback13(() => {
if (!isEnabled)
return;
if (hasLeft) {
setHasLeft(false);
onReturn?.();
}
}, [isEnabled, hasLeft, onReturn]);
const enable = useCallback13(() => {
setIsEnabled(true);
}, []);
const disable = useCallback13(() => {
setIsEnabled(false);
}, []);
useEffect13(() => {
if (isEnabled) {
document.addEventListener("mouseout", handleMouseOut);
document.addEventListener("mouseover", handleMouseOver);
}
return () => {
document.removeEventListener("mouseout", handleMouseOut);
document.removeEventListener("mouseover", handleMouseOver);
};
}, [isEnabled, handleMouseOut, handleMouseOver]);
return { hasLeft, enable, disable };
}
// src/hooks/useQueue.ts
import { useCallback as useCallback14, useMemo, useState as useState13 } from "react";
function useQueue(initialValues = [], options = {}) {
const { maxSize, onFull, onEmpty, equalityFn = (a, b) => a === b } = options;
const [queue, setQueue] = useState13(initialValues);
const enqueue = useCallback14(
(item) => {
setQueue((q) => {
if (maxSize && q.length >= maxSize) {
onFull?.();
return q;
}
return [...q, item];
});
},
[maxSize, onFull]
);
const dequeue = useCallback14(() => {
if (queue.length === 0) {
return void 0;
}
const itemToDequeue = queue[0];
setQueue((currentQueue) => {
if (currentQueue.length === 0)
return currentQueue;
const newQueue = currentQueue.slice(1);
if (onEmpty && currentQueue.length > 0 && newQueue.length === 0) {
onEmpty();
}
return newQueue;
});
return itemToDequeue;
}, [queue, onEmpty]);
const clear = useCallback14(() => {
if (onEmpty && queue.length > 0) {
onEmpty();
}
setQueue([]);
}, [onEmpty, queue]);
const peek = useCallback14(() => queue[0], [queue]);
const peekLast = useCallback14(() => queue[queue.length - 1], [queue]);
const contains = useCallback14((item) => queue.some((i) => equalityFn(i, item)), [queue, equalityFn]);
const state = useMemo(
() => ({
isEmpty: queue.length === 0,
size: queue.length,
toArray: () => [...queue]
}),
[queue]
);
return {
enqueue,
dequeue,
clear,
peek,
peekLast,
contains,
...state
};
}
// src/hooks/useSessionStorage.ts
import { useCallback as useCallback15, useEffect as useEffect14, useRef as useRef9, useState as useState14 } from "react";
function useSessionStorage(key, initialValue, options = {}) {
const {
serializer = JSON.stringify,
deserializer = JSON.parse,
onError = console.error,
syncTabs = false
} = options;
const mounted = useRef9(true);
const [storedValue, setStoredValue] = useState14(() => {
try {
const item = window.sessionStorage.getItem(key);
return item ? deserializer(item) : initialValue;
} catch (error) {
onError(error);
return initialValue;
}
});
const setValue = useCallback15(
(value) => {
setStoredValue((prev) => {
try {
const newValue = value instanceof Function ? value(prev) : value;
window.sessionStorage.setItem(key, serializer(newValue));
return newValue;
} catch (error) {
onError(error);
return prev;
}
});
},
[key, serializer, onError]
);
const handleStorageChange = useCallback15(
(event) => {
if (event.key === key && event.newValue !== null && mounted.current) {
try {
const newValue = deserializer(event.newValue);
setStoredValue(newValue);
} catch (error) {
onError(error);
}
}
},
[key, deserializer, onError]
);
useEffect14(() => {
if (syncTabs) {
window.addEventListener("storage", handleStorageChange);
return () => {
window.removeEventListener("storage", handleStorageChange);
};
}
}, [syncTabs, handleStorageChange]);
useEffect14(
() => () => {
mounted.current = false;
},
[]
);
return [storedValue, setValue];
}
// src/hooks/useTimeout.ts
import { useCallback as useCallback16, useEffect as useEffect15, useRef as useRef10, useState as useState15 } from "react";
function useTimeout({ callback, delay, autoStart = true }) {
const savedCallback = useRef10(() => {
});
const timeoutId = useRef10(null);
const [isPending, setIsPending] = useState15(false);
useEffect15(() => {
savedCallback.current = callback;
}, [callback]);
const clear = useCallback16(() => {
if (timeoutId.current !== null) {
clearTimeout(timeoutId.current);
timeoutId.current = null;
}
setIsPending(false);
}, []);
const start = useCallback16(() => {
clear();
if (delay !== null) {
setIsPending(true);
timeoutId.current = window.setTimeout(() => {
savedCallback.current();
setIsPending(false);
}, delay);
}
}, [delay, clear]);
const reset = useCallback16(() => {
clear();
start();
}, [clear, start]);
useEffect15(() => {
if (autoStart && delay !== null) {
start();
}
return clear;
}, [delay, autoStart, start, clear]);
return {
isPending,
start,
cancel: clear,
reset
};
}
// src/hooks/useToggle.ts
import { useCallback as useCallback17, useEffect as useEffect16, useRef as useRef11, useState as useState16 } from "react";
function useToggle({
initialValue = false,
onChange,
persist = false,
storageKey = "useToggle"
} = {}) {
const [value, setValue] = useState16(() => {
if (persist) {
const stored = localStorage.getItem(storageKey);
return stored ? JSON.parse(stored) : initialValue;
}
return initialValue;
});
const mounted = useRef11(true);
const updateValue = useCallback17(
(newValue) => {
if (mounted.current) {
setValue(newValue);
onChange?.(newValue);
if (persist) {
localStorage.setItem(storageKey, JSON.stringify(newValue));
}
}
},
[onChange, persist, storageKey]
);
const toggle = useCallback17(() => updateValue(!value), [value, updateValue]);
const setTrue = useCallback17(() => updateValue(true), [updateValue]);
const setFalse = useCallback17(() => updateValue(false), [updateValue]);
useEffect16(
() => () => {
mounted.current = false;
},
[]
);
return {
value,
toggle,
setTrue,
setFalse,
setValue: updateValue
};
}
// src/hooks/useVisibilityChange.ts
import { useCallback as useCallback18, useEffect as useEffect17, useState as useState17 } from "react";
function useVisibilityChange({
onVisible,
onHidden,
startImmediately = true
} = {}) {
const [isVisible, setIsVisible] = useState17(!document.hidden);
const [isMonitoring, setIsMonitoring] = useState17(startImmediately);
const handleVisibilityChange = useCallback18(() => {
const newVisibility = !document.hidden;
setIsVisible(newVisibility);
if (newVisibility) {
onVisible?.();
} else {
onHidden?.();
}
}, [onVisible, onHidden, isMonitoring]);
const start = useCallback18(() => setIsMonitoring(true), []);
const stop = useCallback18(() => setIsMonitoring(false), []);
useEffect17(() => {
if (isMonitoring) {
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
} else {
document.removeEventListener("visibilitychange", handleVisibilityChange);
}
}, [isMonitoring, handleVisibilityChange]);
return { isVisible, start, stop };
}
// src/hooks/useWindowWidth.ts
import { useCallback as useCallback19, useEffect as useEffect18, useRef as useRef12, useState as useState18 } from "react";
function useWindowWidth({
debounceDelay = 250,
startImmediately = true,
initialWidth = typeof window !== "undefined" ? window.innerWidth : 0,
onChange
} = {}) {
const [width, setWidth] = useState18(initialWidth);
const [isMonitoring, setIsMonitoring] = useState18(startImmediately);
const timeoutRef = useRef12(null);
const handleResize = useCallback19(() => {
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
}
timeoutRef.current = window.setTimeout(() => {
const newWidth = window.innerWidth;
setWidth(newWidth);
onChange?.(newWidth);
}, debounceDelay);
}, [debounceDelay, onChange]);
const start = useCallback19(() => setIsMonitoring(true), []);
const stop = useCallback19(() => setIsMonitoring(false), []);
useEffect18(() => {
if (isMonitoring) {
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
}
};
} else {
window.removeEventListener("resize", handleResize);
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
}
}, [isMonitoring, handleResize]);
return { width, start, stop };
}
export {
useBoolean,
useClickOutside,
useCopyToClipboard,
useDebounce,
useHandledInterval,
useIdleness,
useInterval,
useLocalStorage,
useLockBodyScroll,
useLogger,
useMeasure,
useMediaQuery,
useNetworkState,
usePageLeave,
useQueue,
useSessionStorage,
useTimeout,
useToggle,
useVisibilityChange,
useWindowWidth
};