@gilbarbara/hooks
Version:
Collection of useful React hooks
1,276 lines (1,229 loc) • 36.1 kB
JavaScript
// src/useBreakpoint.tsx
import { useCallback, useEffect, useMemo, useState } from "react";
// src/utils.ts
function canUseDOM() {
return !!(typeof window !== "undefined" && window?.document?.createElement);
}
function getElement(target) {
if (!canUseDOM()) {
return null;
}
let targetEl;
if (typeof target === "string") {
targetEl = document.querySelector(target);
} else {
targetEl = target && "current" in target ? target.current : target;
}
return targetEl;
}
function isPlainObject(value) {
if (Object.prototype.toString.call(value) !== "[object Object]") {
return false;
}
const prototype = Object.getPrototypeOf(value);
return prototype === null || prototype === Object.getPrototypeOf({});
}
function isPrimitive(value) {
return value !== Object(value);
}
function isString(value) {
return typeof value === "string";
}
function isURL(value) {
if (!isString(value)) {
return false;
}
try {
new URL(value);
return true;
} catch {
return false;
}
}
function noop() {
return void 0;
}
function off(target, ...rest) {
if (target && target.removeEventListener) {
target.removeEventListener(...rest);
}
}
function on(target, ...rest) {
if (target && target.addEventListener) {
target.addEventListener(...rest);
}
}
function validateDependencies(dependencies, name, fallback) {
if (process.env.NODE_ENV !== "production") {
if (!(dependencies instanceof Array) || !dependencies.length) {
console.warn(
`${name} should not be used with no dependencies. Use React.${fallback} instead.`
);
}
if (dependencies.length && dependencies.every(isPrimitive)) {
console.warn(
`${name} should not be used with dependencies that are all primitive values. Use React.${fallback} instead.`
);
}
}
}
// src/useBreakpoint.tsx
var defaultBreakpoints = { xs: 0, sm: 400, md: 768, lg: 1024, xl: 1280 };
function useBreakpoint(customBreakpoints, initialWidth = Infinity, initialHeight = Infinity) {
const breakpoints = customBreakpoints ?? defaultBreakpoints;
const sizes = useMemo(
() => Object.entries(breakpoints).sort(([, aSize], [, bSize]) => bSize - aSize),
[breakpoints]
);
const smallestBreakpoint = sizes[sizes.length - 1];
if (smallestBreakpoint[1] !== 0) {
if (process.env.NODE_ENV !== "production") {
console.warn(`The "${smallestBreakpoint[0]}" breakpoint should be 0`);
}
}
const getScreen = useCallback(() => {
const height = canUseDOM() ? window.innerHeight : initialHeight;
const width = canUseDOM() ? window.innerWidth : initialWidth;
const size = sizes.find(([, s]) => s <= width) || sizes[0];
const orientation = width > height ? "landscape" : "portrait";
return {
between: (min, max, andOrientation) => width >= breakpoints[min] && width < breakpoints[max] && (!andOrientation || andOrientation === orientation),
min: (breakpoint, andOrientation) => width >= breakpoints[breakpoint] && (!andOrientation || andOrientation === orientation),
max: (breakpoint, andOrientation) => width < breakpoints[breakpoint] && (!andOrientation || andOrientation === orientation),
orientation,
size: size[0]
};
}, [breakpoints, initialHeight, initialWidth, sizes]);
const [screen, setScreen] = useState(getScreen);
useEffect(() => {
const onResize = () => {
setScreen((previous) => {
const current = getScreen();
return current.size !== previous.size || current.orientation !== previous.orientation ? current : previous;
});
};
on(window, "resize", onResize);
return () => off(window, "resize", onResize);
}, [getScreen]);
return screen;
}
// src/useCallbackDeepCompare.ts
import { useCallback as useCallback2, useRef } from "react";
import deepEqual from "@gilbarbara/deep-equal";
function useCallbackDeepCompare(callback, dependencies) {
validateDependencies(dependencies, "useCallbackDeepCompare", "useCallback");
const ref = useRef(dependencies);
const callbackRef = useRef(callback);
if (!deepEqual(dependencies, ref.current)) {
ref.current = dependencies;
callbackRef.current = callback;
}
return useCallback2(
(...arguments_) => callbackRef.current(...arguments_),
// eslint-disable-next-line react-hooks/exhaustive-deps
ref.current
);
}
// src/useClickOutside.ts
import { useEffect as useEffect3, useRef as useRef4 } from "react";
// src/useMemoizedValue.ts
import { useState as useState2 } from "react";
// src/useEffectDeepCompare.ts
import { useEffect as useEffect2, useRef as useRef2 } from "react";
import deepEqual2 from "@gilbarbara/deep-equal";
function useEffectDeepCompare(effect, dependencies) {
validateDependencies(dependencies, "useEffectDeepCompare", "useEffect");
const ref = useRef2(dependencies);
if (!deepEqual2(dependencies, ref.current)) {
ref.current = dependencies;
}
useEffect2(effect, ref.current);
}
// src/useIsFirstRender.ts
import { useRef as useRef3 } from "react";
function useIsFirstRender() {
const isFirstRender = useRef3(true);
if (isFirstRender.current) {
isFirstRender.current = false;
return true;
}
return isFirstRender.current;
}
// src/useMemoizedValue.ts
function useMemoizedValue(value) {
const [stableValue, setStableValue] = useState2(() => value);
const isFirstRender = useIsFirstRender();
useEffectDeepCompare(() => {
if (isFirstRender) {
return;
}
setStableValue(() => value);
}, [value]);
return stableValue;
}
// src/useClickOutside.ts
function useClickOutside(callback) {
const ref = useRef4(null);
const memoizedCallback = useMemoizedValue(callback);
useEffect3(() => {
const handleClick = (event) => {
if (!ref.current?.contains(event.target)) {
memoizedCallback();
}
};
on(document, "click", handleClick);
return () => {
off(document, "click", handleClick);
};
}, [memoizedCallback]);
return ref;
}
// src/useDataChanges.tsx
import { useRef as useRef5, useState as useState3 } from "react";
import deepEqual3 from "@gilbarbara/deep-equal";
// src/useUpdateEffect.ts
import { useEffect as useEffect4 } from "react";
function useUpdateEffect(effect, dependencies) {
const isFirstRender = useIsFirstRender();
useEffect4(() => {
if (!isFirstRender) {
return effect();
}
return void 0;
}, dependencies);
}
// src/useDataChanges.tsx
function useDataChanges(data, nameOrOptions = {}) {
const previousData = useRef5(data);
const [changes, setChanges] = useState3(void 0);
const {
comparison = "shallow",
name,
only,
skipLog = false
} = useMemoizedValue(isString(nameOrOptions) ? { name: nameOrOptions } : nameOrOptions);
useUpdateEffect(() => {
const keysToCheck = only ?? Object.keys({ ...previousData.current, ...data });
const changesObject = {};
keysToCheck.forEach((key) => {
const hasChanged = comparison === "deep" ? !deepEqual3(previousData.current[key], data[key]) : previousData.current[key] !== data[key];
if (hasChanged) {
changesObject[key] = {
from: previousData.current[key],
to: data[key]
};
}
});
const hasChanges = Object.keys(changesObject).length > 0;
setChanges(hasChanges ? changesObject : void 0);
if (hasChanges && !skipLog) {
const nameToken = name ? `: ${name}` : "";
console.log(`[data-changes${nameToken}]`, changesObject);
}
previousData.current = data;
}, [name, data, only, comparison, skipLog]);
return changes;
}
// src/useDebounce.ts
import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef6 } from "react";
function useDebounce(callback, delayMs = 250, dependencies = []) {
const status = useRef6("pending");
const timeout = useRef6(null);
const savedCallback = useRef6(callback);
const isFirstRender = useIsFirstRender();
const clear = useCallback3(() => {
status.current = "cancelled";
timeout.current && clearTimeout(timeout.current);
}, []);
const set = useCallback3(() => {
status.current = "pending";
timeout.current && clearTimeout(timeout.current);
timeout.current = setTimeout(() => {
status.current = "completed";
savedCallback.current();
}, delayMs);
}, [delayMs]);
const getStatus = useCallback3(() => status.current, []);
useEffect5(() => {
savedCallback.current = callback;
}, [callback]);
useEffectDeepCompare(() => {
if (!isFirstRender) {
set();
return clear;
}
return void 0;
}, [set, clear, dependencies, delayMs]);
return { cancel: clear, getStatus };
}
// src/useEffectOnce.ts
import { useEffect as useEffect6, useRef as useRef7 } from "react";
function useEffectOnce(effect) {
const destroyFn = useRef7(null);
const effectCalled = useRef7(false);
const effectFn = useRef7(effect);
useEffect6(() => {
if (!effectCalled.current) {
destroyFn.current = effectFn.current();
effectCalled.current = true;
}
return () => {
if (destroyFn.current) {
destroyFn.current();
destroyFn.current = null;
}
};
}, []);
}
// src/useElementMeasure.tsx
import { useState as useState5 } from "react";
// src/defaults.ts
var defaultElementDimensions = {
absoluteHeight: 0,
absoluteWidth: 0,
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0,
x: 0,
y: 0
};
// src/useIsomorphicLayoutEffect.ts
import { useEffect as useEffect7, useLayoutEffect } from "react";
var useIsomorphicLayoutEffect = canUseDOM() ? useLayoutEffect : useEffect7;
// src/useResizeObserver.ts
import { useMemo as useMemo2, useRef as useRef8, useState as useState4 } from "react";
function useResizeObserver(target, debounce = 0) {
const [element, setElement] = useState4(getElement(target));
const [value, setValue] = useState4();
const timeoutRef = useRef8(null);
const isFirstCall = useRef8(true);
const observer = useMemo2(() => {
if (!canUseDOM()) {
return {};
}
return new window.ResizeObserver((entries) => {
if (debounce && !isFirstCall.current) {
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
}
timeoutRef.current = window.setTimeout(() => {
setValue(entries[0]);
}, debounce);
return;
}
setValue(entries[0]);
isFirstCall.current = false;
});
}, [debounce]);
useIsomorphicLayoutEffect(() => {
setElement(getElement(target));
}, [target]);
useIsomorphicLayoutEffect(() => {
if (!canUseDOM() || !(observer instanceof ResizeObserver)) {
return () => void 0;
}
if (!element) {
return () => void 0;
}
observer.observe(element);
return () => {
observer.disconnect();
};
}, [element, observer]);
return value;
}
// src/useElementMeasure.tsx
function getElementMeasure(element) {
if (!canUseDOM() || !element) {
return defaultElementDimensions;
}
const { bottom, height, left, right, top, width, x, y } = element.getBoundingClientRect();
const {
borderBottom,
borderLeft,
borderRight,
borderTop,
paddingBottom,
paddingLeft,
paddingRight,
paddingTop
} = getComputedStyle(element);
return {
absoluteHeight: height - parseFloatValue(paddingTop) - parseFloatValue(paddingBottom) - parseFloatValue(borderTop) - parseFloatValue(borderBottom),
absoluteWidth: width - parseFloatValue(paddingLeft) - parseFloatValue(paddingRight) - parseFloatValue(borderLeft) - parseFloatValue(borderRight),
bottom,
height,
left,
right,
top,
width,
x,
y
};
}
function parseFloatValue(value) {
const parsed = parseFloat(value);
return Number.isNaN(parsed) ? 0 : parsed;
}
function useElementMeasure(target, debounce = 0) {
const [element, setElement] = useState5(getElement(target));
const [dimensions, setDimensions] = useState5(getElementMeasure(element));
const entry = useResizeObserver(element, debounce);
useIsomorphicLayoutEffect(() => {
const nextElement = getElement(target);
setElement(nextElement);
setDimensions(getElementMeasure(nextElement));
}, [target]);
useIsomorphicLayoutEffect(() => {
if (!entry) {
return;
}
const { bottom, height, left, right, top, width, x, y } = entry.contentRect;
const { blockSize, inlineSize } = entry.borderBoxSize[0];
setDimensions({
absoluteHeight: blockSize,
absoluteWidth: inlineSize,
bottom,
height,
left,
right,
top,
width,
x,
y
});
}, [entry]);
return dimensions;
}
// src/useFetch.ts
import { useCallback as useCallback6, useEffect as useEffect10, useMemo as useMemo3 } from "react";
// src/useIsMounted.ts
import { useCallback as useCallback4, useEffect as useEffect8, useRef as useRef9 } from "react";
function useIsMounted() {
const isMounted = useRef9(false);
useEffect8(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
return useCallback4(() => isMounted.current, []);
}
// src/usePrevious.ts
import { useEffect as useEffect9, useRef as useRef10 } from "react";
function usePrevious(state) {
const ref = useRef10(void 0);
useEffect9(() => {
ref.current = state;
});
return ref.current;
}
// src/useSetState.ts
import { useCallback as useCallback5, useState as useState6 } from "react";
function useSetState(initialState = {}) {
const [state, set] = useState6(initialState);
const setState = useCallback5((patch) => {
set((previousState) => ({
...previousState,
...patch instanceof Function ? patch(previousState) : patch
}));
}, []);
return [state, setState];
}
// src/useFetch.ts
var USE_FETCH_STATUS = {
IDLE: "IDLE",
LOADING: "LOADING",
SUCCESS: "SUCCESS",
ERROR: "ERROR"
};
var globalCache = /* @__PURE__ */ new Map();
async function request(options) {
const {
body = void 0,
headers = {},
method = "GET",
mode = "cors",
type = "json",
url = ""
} = options;
const contentTypes = {
json: "application/json",
urlencoded: "application/x-www-form-urlencoded"
};
const params = {
body: void 0,
cache: "no-store",
headers: {
Accept: "application/json",
"Content-Type": contentTypes[type],
...headers
},
method,
mode,
credentials: void 0
};
if (body) {
params.body = isPlainObject(body) && type === "json" ? JSON.stringify(body) : body;
}
try {
const response = await fetch(url, params);
let content;
try {
content = await response.json();
} catch {
content = await response.text();
}
if (response.status > 299) {
const error = new Error(response.statusText);
error.status = response.status;
error.response = content;
throw error;
}
return content;
} catch (error) {
const fetchError = new Error("Network request failed");
fetchError.status = 0;
fetchError.response = error instanceof Error ? error.message : error;
throw fetchError;
}
}
var useFetchCache = {
/**
* Retrieve data from the cache.
* @param url The URL to retrieve data for.
* @returns Cached data or `undefined` if not found or expired.
*/
get(url) {
const cached = globalCache.get(url);
if (cached && Date.now() < cached.expiry) {
return cached;
}
globalCache.delete(url);
return void 0;
},
/**
* Set data in the cache.
* @param url The URL to cache data for.
* @param data The data to cache.
* @param ttl Time-to-live in milliseconds.
*/
set(url, data, ttl) {
globalCache.set(url, { data, expiry: Date.now() + ttl });
},
/**
* Clear the cache.
* @param url Optional URL to clear. Clears all cache if not specified.
*/
clear(url) {
if (url) {
globalCache.delete(url);
} else {
globalCache.clear();
}
},
/**
* Check if a URL is cached and still valid.
* @param url The URL to check.
* @returns `true` if cached and valid, `false` otherwise.
*/
has(url) {
const cached = globalCache.get(url);
return !!cached && Date.now() < cached.expiry;
}
};
function useFetch(urlOrOptions) {
const {
cacheTTL = 0,
onError,
onFinally,
onLoading,
onSuccess,
retries = 0,
retryDelay = (attempt) => attempt * 1e3,
wait = false,
...options
} = useMemoizedValue(
isURL(urlOrOptions) ? { type: "json", url: urlOrOptions } : urlOrOptions
);
const [{ data, error, isCached, retryCount, status, url }, setState] = useSetState({
data: void 0,
error: void 0,
isCached: false,
retryCount: null,
status: USE_FETCH_STATUS.IDLE,
url: options.url
});
const isMounted = useIsMounted();
const previousRetryCount = usePrevious(retryCount);
if (!isPlainObject(options) || !isURL(url)) {
throw new Error("Expected an options object or URL");
}
const getData = useCallback6(
(eraseData) => {
setState((s) => ({
data: eraseData ? void 0 : s.data,
error: void 0,
isCached: false,
status: USE_FETCH_STATUS.LOADING
}));
onLoading?.();
if (cacheTTL && useFetchCache.has(url) && !eraseData) {
const cached = useFetchCache.get(url);
if (cached) {
setState({
data: cached.data,
error: void 0,
isCached: true,
status: USE_FETCH_STATUS.SUCCESS
});
onSuccess?.(cached.data);
onFinally?.();
return;
}
}
request({ ...options }).then((response) => {
if (!isMounted()) {
return;
}
if (cacheTTL) {
useFetchCache.set(url, response, cacheTTL);
}
setState({
data: response,
retryCount: null,
status: USE_FETCH_STATUS.SUCCESS
});
onSuccess?.(response);
}).catch((responseError) => {
if (!isMounted()) {
return;
}
const counter = retryCount ?? 0;
if (!retries || counter >= retries) {
setState({
error: responseError,
status: USE_FETCH_STATUS.ERROR
});
onError?.(responseError);
}
if (retries && counter < retries) {
setState({
retryCount: counter + 1
});
}
}).finally(() => {
if (isMounted()) {
onFinally?.();
}
});
},
[
cacheTTL,
isMounted,
onError,
onFinally,
onLoading,
onSuccess,
options,
retries,
retryCount,
setState,
url
]
);
useEffect10(() => {
if (url !== options.url) {
setState({
data: void 0,
error: void 0,
status: USE_FETCH_STATUS.IDLE,
url: options.url
});
}
}, [options.url, setState, url]);
useEffect10(() => {
if (status === USE_FETCH_STATUS.IDLE && !wait) {
getData();
}
}, [getData, status, wait]);
useEffect10(() => {
if (retries && typeof retryCount === "number" && retryCount !== previousRetryCount) {
setTimeout(getData, typeof retryDelay === "function" ? retryDelay(retryCount) : retryDelay);
}
}, [getData, previousRetryCount, retries, retryCount, retryDelay]);
const isError = useCallback6(() => status === USE_FETCH_STATUS.ERROR, [status]);
const isFetched = useCallback6(
() => [USE_FETCH_STATUS.SUCCESS, USE_FETCH_STATUS.ERROR].includes(status),
[status]
);
const isLoading = useCallback6(() => status === USE_FETCH_STATUS.LOADING, [status]);
const isPaused = useCallback6(() => status === USE_FETCH_STATUS.IDLE && wait, [status, wait]);
const isSuccess = useCallback6(() => status === USE_FETCH_STATUS.SUCCESS, [status]);
const refetch = useCallback6((eraseData = false) => getData(eraseData), [getData]);
return useMemo3(
() => ({
data,
error,
isCached,
isError,
isFetched,
isLoading,
isPaused,
isSuccess,
refetch,
retryCount,
status,
url
}),
[
data,
error,
isCached,
isError,
isFetched,
isLoading,
isPaused,
isSuccess,
refetch,
retryCount,
status,
url
]
);
}
// src/useHasChanged.ts
import { useEffect as useEffect11 } from "react";
function useHasChanged(value, callback) {
const previous = usePrevious(value);
const hasChanged = typeof previous !== "undefined" && previous !== value;
useEffect11(() => {
if (hasChanged) {
callback?.(previous);
}
}, [callback, hasChanged, previous]);
return [hasChanged, previous];
}
// src/useIntersectionObserver.ts
import { useEffect as useEffect12, useMemo as useMemo4, useState as useState7 } from "react";
function useIntersectionObserver(target, options) {
const { delay = 0, once = false, root = null, rootMargin = "0%", threshold = 0 } = options || {};
const [value, setValue] = useState7();
const disabled = value?.isIntersecting && once;
const observer = useMemo4(() => {
if (!canUseDOM()) {
return {};
}
return new IntersectionObserver(
([entry]) => {
if (delay) {
setTimeout(() => setValue(entry), delay);
return;
}
setValue(entry);
},
{ threshold, root, rootMargin }
);
}, [delay, root, rootMargin, threshold]);
useEffect12(() => {
if (!canUseDOM() || !(observer instanceof IntersectionObserver) || disabled) {
return () => void 0;
}
const element = getElement(target);
if (!element) {
return () => void 0;
}
observer.observe(element);
return () => observer.disconnect();
}, [target, root, rootMargin, disabled, observer]);
return value;
}
// src/useInterval.ts
import { useEffect as useEffect13, useRef as useRef11 } from "react";
function useInterval(callback, delayMs = 100) {
const savedCallback = useRef11(callback);
useEffect13(() => {
savedCallback.current = callback;
});
useEffect13(() => {
if (delayMs !== null) {
const interval = setInterval(() => savedCallback.current(), delayMs);
return () => {
clearInterval(interval);
};
}
return void 0;
}, [delayMs]);
}
// src/useLatest.ts
import { useEffect as useEffect14, useRef as useRef12 } from "react";
function useLatest(value) {
const ref = useRef12(value);
useEffect14(() => {
ref.current = value;
});
return ref;
}
// src/useLifecycleHooks.ts
import { useEffect as useEffect15 } from "react";
function useLifecycleHooks(mount, unmount) {
useEffect15(() => {
mount();
return unmount;
}, []);
}
// src/useLocalStorage.ts
import {
useCallback as useCallback7,
useLayoutEffect as useLayoutEffect2,
useMemo as useMemo5,
useRef as useRef13,
useState as useState8
} from "react";
function useLocalStorageHook(key, initialValue, options) {
if (!key) {
throw new Error('useLocalStorage: "key" is required');
}
const deserializer = useMemo5(
() => options?.raw ? (value) => value : options?.deserializer ?? JSON.parse,
[options]
);
const serializer = useMemo5(
() => options?.raw ? String : options?.serializer ?? JSON.stringify,
[options]
);
const initializer = useRef13((k) => {
try {
const localStorageValue = localStorage.getItem(k);
if (localStorageValue !== null) {
return deserializer(localStorageValue);
}
initialValue && localStorage.setItem(k, serializer(initialValue));
return initialValue;
} catch {
return initialValue;
}
});
const [state, setState] = useState8(() => initializer.current(key));
useLayoutEffect2(() => setState(initializer.current(key)), [key]);
const set = useCallback7(
(patch) => {
try {
const newState = patch instanceof Function ? patch(state) : patch;
if (typeof newState === "undefined") {
return;
}
let value;
if (options) {
if (options.raw) {
value = typeof newState === "string" ? newState : JSON.stringify(newState);
} else if (options?.serializer) {
value = options.serializer(newState);
} else {
value = JSON.stringify(newState);
}
} else {
value = JSON.stringify(newState);
}
localStorage.setItem(key, value);
setState(deserializer(value));
} catch {
}
},
[deserializer, key, options, state]
);
const remove = useCallback7(() => {
try {
localStorage.removeItem(key);
setState(void 0);
} catch {
}
}, [key, setState]);
return [state, set, remove];
}
function useLocalStorageSSR(_key, initialValue, _options) {
return [initialValue, noop, noop];
}
var useLocalStorage = canUseDOM() ? useLocalStorageHook : useLocalStorageSSR;
// src/useLocation.ts
function useLocation() {
const { hash, host, hostname, href, origin, pathname, port, protocol, search } = window.location;
const query = search ? search.slice(1).split("&").reduce((acc, pair) => {
const [key, value] = pair.split("=");
if (key && value) {
acc[key] = value;
}
return acc;
}, {}) : {};
return { hash, host, hostname, href, origin, pathname, port, protocol, query, search };
}
// src/useMediaQuery.ts
import { useEffect as useEffect16, useState as useState9 } from "react";
function useMediaQuery(input) {
const getMatches = (query) => {
if (!canUseDOM()) {
return false;
}
return window.matchMedia(query).matches;
};
const [matches, setMatches] = useState9(getMatches(input));
function handleChange() {
setMatches(getMatches(input));
}
useEffect16(() => {
const matchMedia = window.matchMedia(input);
handleChange();
try {
matchMedia.addEventListener("change", handleChange);
} catch {
matchMedia.addListener(handleChange);
}
return () => {
try {
matchMedia.removeEventListener("change", handleChange);
} catch {
matchMedia.removeListener(handleChange);
}
};
}, [input]);
return matches;
}
// src/useMemoDeepCompare.ts
import { useMemo as useMemo6, useRef as useRef14 } from "react";
import deepEqual4 from "@gilbarbara/deep-equal";
function useMemoDeepCompare(factory, dependencies) {
validateDependencies(dependencies, "useMemoDeepCompare", "useMemo");
const ref = useRef14(dependencies);
if (!deepEqual4(dependencies, ref.current)) {
ref.current = dependencies;
}
return useMemo6(factory, ref.current);
}
// src/useMergeRefs.tsx
import { useCallback as useCallback8 } from "react";
function useMergeRefs(...refs) {
return useCallback8(
(value) => {
for (const ref of refs) {
if (typeof ref === "function") {
ref(value);
} else if (ref && typeof ref === "object") {
ref.current = value;
}
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
refs
);
}
// src/useMount.ts
import { useEffect as useEffect17 } from "react";
function useMount(callback) {
useEffect17(() => {
callback();
}, []);
}
// src/useOnce.tsx
import { useRef as useRef15 } from "react";
function useOnce(callback) {
const hasBeenCalled = useRef15(false);
if (hasBeenCalled.current) {
return;
}
callback();
hasBeenCalled.current = true;
}
// src/usePersistentState.ts
function getState(initialState, savedState, shouldOverride, restoreProperties) {
if (shouldOverride) {
const initialStateKeys = Object.keys(initialState);
const savedStateKeys = savedState ? Object.keys(savedState) : [];
const restorePropertiesKeys = restoreProperties ? Object.keys(restoreProperties) : [];
if (![...initialStateKeys, ...restorePropertiesKeys].every((k) => savedStateKeys.includes(k))) {
return { ...initialState, ...restoreProperties };
}
return { ...savedState, ...restoreProperties };
}
return { ...savedState, ...restoreProperties };
}
function usePersistentState(key, initialState, options) {
const { overrideDivergentSavedState = false, resetProperties } = options || {};
const [value, setValue, remove] = useLocalStorage(key, initialState);
const [state, setState] = useSetState(
getState(initialState, value, overrideDivergentSavedState, resetProperties)
);
useEffectDeepCompare(() => {
setValue(state);
}, [setValue, state]);
return [state, setState, remove];
}
// src/useRenderCount.tsx
import { useEffect as useEffect18, useRef as useRef16 } from "react";
function useRenderCount(name) {
const count = useRef16(1);
useEffect18(() => {
count.current += 1;
});
console.log(
`%c${name || "RenderCount"}: %c${count.current}`,
"font-size: 14px; font-weight: bold;",
"color: #999; font-size: 14px;"
);
return count.current;
}
// src/useScript.tsx
import { useCallback as useCallback9, useEffect as useEffect19, useMemo as useMemo7, useRef as useRef17, useState as useState10 } from "react";
function useScript(src, idOrOptions = {}) {
const options = useMemo7(
() => isString(idOrOptions) ? { id: idOrOptions } : idOrOptions,
[idOrOptions]
);
const script = useRef17(null);
const [state, setState] = useState10({
loaded: false,
error: false
});
const onLoad = useCallback9(() => {
setState({
loaded: true,
error: false
});
}, []);
const onError = useCallback9(() => {
if (script.current) {
script.current.remove();
}
setState({
loaded: false,
error: true
});
}, []);
useEffect19(
() => {
if (!canUseDOM() || script.current) {
return void 0;
}
const element = document.createElement("script");
element.async = options.async ?? true;
element.defer = options.defer ?? false;
element.type = options.type || "text/javascript";
element.id = options.id || src;
element.src = src;
script.current = element;
const { current } = script;
on(current, "load", onLoad);
on(current, "error", onError);
document.body.appendChild(current);
return () => {
off(current, "load", onLoad);
off(current, "error", onError);
};
},
[onError, onLoad, options, src]
// Only re-run effect if script src changes
);
return [state.loaded, state.error];
}
// src/useThrottle.ts
import { useEffect as useEffect21, useRef as useRef18, useState as useState11 } from "react";
// src/useUnmount.ts
import { useEffect as useEffect20 } from "react";
function useUnmount(callback) {
const callbackRef = useLatest(callback);
useEffect20(() => {
return () => callbackRef.current();
}, []);
}
// src/useThrottle.ts
function useThrottle(callback, delayMs = 500, trailing = false) {
const [now, setNow] = useState11(0);
const callbackRef = useRef18(callback);
const hasPendingCall = useRef18(false);
const timer = useRef18(void 0);
useEffect21(() => {
callbackRef.current = callback;
}, [callback]);
useEffect21(() => {
if (!now) {
return;
}
if (!timer.current) {
callbackRef.current();
const timerCallback = () => {
if (hasPendingCall.current) {
hasPendingCall.current = false;
if (trailing) {
callbackRef.current();
}
timer.current = void 0;
} else {
timer.current = void 0;
}
};
timer.current = window.setTimeout(timerCallback, delayMs);
} else {
hasPendingCall.current = true;
}
}, [delayMs, now, trailing]);
useUnmount(() => {
window.clearTimeout(timer.current);
timer.current = void 0;
});
return () => setNow(Date.now());
}
// src/useThrottleValue.ts
import { useEffect as useEffect22, useRef as useRef19, useState as useState12 } from "react";
function useThrottleValue(value, delayMs) {
const [throttledValue, setThrottledValue] = useState12(value);
const hasNextValue = useRef19(false);
const nextValue = useRef19(null);
const timer = useRef19(void 0);
useEffect22(() => {
if (!timer.current) {
setThrottledValue(value);
const timeoutCallback = () => {
if (hasNextValue.current) {
hasNextValue.current = false;
setThrottledValue(nextValue.current);
timer.current = window.setTimeout(timeoutCallback, delayMs);
} else {
timer.current = void 0;
}
};
timer.current = window.setTimeout(timeoutCallback, delayMs);
} else {
hasNextValue.current = true;
nextValue.current = value;
}
}, [delayMs, value]);
useUnmount(() => {
window.clearTimeout(timer.current);
timer.current = void 0;
});
return throttledValue;
}
// src/useTimeout.ts
import { useCallback as useCallback10, useEffect as useEffect23, useRef as useRef20 } from "react";
function useTimeout(callback, delayMs = 0) {
const status = useRef20("pending");
const timeout = useRef20(null);
const savedCallback = useRef20(callback);
const clear = useCallback10(() => {
status.current = "cancelled";
timeout.current && clearTimeout(timeout.current);
}, []);
const set = useCallback10(() => {
status.current = "pending";
timeout.current && clearTimeout(timeout.current);
timeout.current = setTimeout(() => {
status.current = "completed";
savedCallback.current();
}, delayMs);
}, [delayMs]);
const getStatus = useCallback10(() => status.current, []);
useEffect23(() => {
savedCallback.current = callback;
}, [callback]);
useEffect23(() => {
set();
return clear;
}, [set, clear]);
return { cancel: clear, getStatus, reset: set };
}
// src/useToggle.ts
import { useCallback as useCallback11, useReducer } from "react";
function useToggle(initialValue = true) {
const [value, toggle] = useReducer(
(state, nextValue) => typeof nextValue === "boolean" ? nextValue : !state,
initialValue
);
const toggleOn = useCallback11(() => toggle(true), []);
const toggleOff = useCallback11(() => toggle(false), []);
return [value, { toggle, toggleOn, toggleOff }];
}
// src/useUpdate.ts
import { useReducer as useReducer2 } from "react";
var updateReducer = (number) => (number + 1) % 1e6;
function useUpdate() {
const [, update] = useReducer2(updateReducer, 0);
return update;
}
// src/useWindowSize.ts
import { useEffect as useEffect24, useRef as useRef21, useState as useState13 } from "react";
function useWindowSize(debounce = 0) {
const [size, setSize] = useState13({
height: canUseDOM() ? window.innerHeight : 0,
width: canUseDOM() ? window.innerWidth : 0
});
const timeoutRef = useRef21(0);
const handleResize = useRef21(() => {
window.clearTimeout(timeoutRef.current);
timeoutRef.current = window.setTimeout(() => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
}, debounce);
});
useEffect24(() => {
if (!canUseDOM()) {
return () => void 0;
}
const getSize = handleResize.current;
setSize({
height: window.innerHeight,
width: window.innerWidth
});
on(window, "resize", getSize);
return () => {
off(window, "resize", getSize);
};
}, []);
return size;
}
export {
USE_FETCH_STATUS,
useBreakpoint,
useCallbackDeepCompare,
useClickOutside,
useDataChanges,
useDebounce,
useEffectDeepCompare,
useEffectOnce,
useElementMeasure,
useFetch,
useHasChanged,
useIntersectionObserver,
useInterval,
useIsFirstRender,
useIsMounted,
useIsomorphicLayoutEffect,
useLatest,
useLifecycleHooks,
useLocalStorage,
useLocation,
useMediaQuery,
useMemoDeepCompare,
useMemoizedValue,
useMergeRefs,
useMount,
useOnce,
usePersistentState,
usePrevious,
useRenderCount,
useResizeObserver,
useScript,
useSetState,
useThrottle,
useThrottleValue,
useTimeout,
useToggle,
useUnmount,
useUpdate,
useUpdateEffect,
useWindowSize
};
//# sourceMappingURL=index.mjs.map