@hyunjin/hooks
Version:
collection of useful hooks
536 lines (508 loc) • 15.1 kB
JavaScript
// src/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.ts
import { useEffect, useLayoutEffect } from "react";
var canUseDOM = !!(typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined");
var useIsomorphicLayoutEffect = canUseDOM ? useLayoutEffect : useEffect;
// src/useBodyScrollLock/useBodyScrollLock.ts
var useBodyScrollLock = (enable = true) => {
useIsomorphicLayoutEffect(() => {
if (enable === false) {
return;
}
const originalStyle = window.getComputedStyle(document.body).overflow;
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = originalStyle;
};
}, [enable]);
};
// src/useBoolean/useBoolean.ts
import { useCallback, useState } from "react";
var useBoolean = (defaultValue = false) => {
if (typeof defaultValue !== "boolean") {
throw new Error("defaultValue must be `true` or `false`");
}
const [value, setValue] = useState(defaultValue);
const setTrue = useCallback(() => {
setValue(true);
}, []);
const setFalse = useCallback(() => {
setValue(false);
}, []);
const toggle = useCallback(() => {
setValue((x) => !x);
}, []);
return { value, setValue, setTrue, setFalse, toggle };
};
// src/useCallbackOnce/useCallbackOnce.ts
import { useCallback as useCallback2, useRef } from "react";
var useCallbackOnce = (callback, deps) => {
const hasFired = useRef(false);
const memoizedCallback = useCallback2((...args) => {
if (hasFired.current) {
return;
}
callback(...args);
hasFired.current = true;
}, deps);
return memoizedCallback;
};
// src/useCopyToClipboard/useCopyToClipboard.ts
import { useCallback as useCallback3, useState as useState2 } from "react";
var useCopyToClipboard = () => {
const [copiedText, setCopiedText] = useState2(null);
const copy = useCallback3(async (text) => {
if (!(navigator == null ? void 0 : navigator.clipboard)) {
console.warn("Clipboard not supported");
return false;
}
try {
await navigator.clipboard.writeText(text);
setCopiedText(text);
return true;
} catch (error) {
console.warn("Copy failed", error);
setCopiedText(null);
return false;
}
}, []);
return {
copiedText,
copy
};
};
// src/useDebounce/useDebounce.ts
import { useEffect as useEffect2, useState as useState3 } from "react";
var useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState3(value);
useEffect2(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay || 500);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
};
// src/useDocumentTitle/useDocumentTitle.ts
var useDocumentTitle = (title) => {
useIsomorphicLayoutEffect(() => {
window.document.title = title;
}, [title]);
};
// src/useEventListener/useEventListener.ts
import { useEffect as useEffect3, useRef as useRef2 } from "react";
var useEventListener = (eventName, handler, element = globalThis, options = {}) => {
const savedHandler = useRef2(handler);
useEffect3(() => {
savedHandler.current = handler;
}, [handler]);
useEffect3(() => {
const isSupported = element && "addEventListener" in element;
if (!isSupported) {
return;
}
const eventListener = (event) => {
if (savedHandler.current) {
savedHandler.current(event);
}
};
const { capture, passive, once } = options;
const opts = { capture, passive, once };
element.addEventListener(eventName, eventListener, opts);
return () => {
element.removeEventListener(eventName, eventListener, opts);
};
}, [eventName, element, options.capture, options.passive, options.once]);
};
// src/useFetch/useFetch.ts
import { useEffect as useEffect4, useReducer, useRef as useRef3 } from "react";
function useFetch(url, options) {
const cache = useRef3({});
const cancelRequest = useRef3(false);
const initialState = {
error: void 0,
data: void 0
};
const fetchReducer = (state2, action) => {
switch (action.type) {
case "loading":
return { ...initialState };
case "fetched":
return { ...initialState, data: action.payload };
case "error":
return { ...initialState, error: action.payload };
default:
return state2;
}
};
const [state, dispatch] = useReducer(fetchReducer, initialState);
useEffect4(() => {
if (!url) {
return;
}
cancelRequest.current = false;
const fetchData = async () => {
dispatch({ type: "loading" });
if (cache.current[url]) {
dispatch({ type: "fetched", payload: cache.current[url] });
return;
}
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(response.statusText);
}
const data = await response.json();
cache.current[url] = data;
if (cancelRequest.current) {
return;
}
dispatch({ type: "fetched", payload: data });
} catch (error) {
if (cancelRequest.current) {
return;
}
dispatch({ type: "error", payload: error });
}
};
void fetchData();
return () => {
cancelRequest.current = true;
};
}, [url]);
return state;
}
// src/useForceUpdate/useForceUpdate.ts
import { useReducer as useReducer2 } from "react";
var updater = (num) => (num + 1) % 1e6;
var useForceUpdate = () => {
const [, forceUpdate] = useReducer2(updater, 0);
return forceUpdate;
};
// src/useInterval/useInterval.ts
import { useEffect as useEffect5, useRef as useRef4 } from "react";
var useInterval = (callback, delay) => {
const savedCallbackRef = useRef4(callback);
useEffect5(() => {
savedCallbackRef.current = callback;
}, [callback]);
useEffect5(() => {
const handler = (...args) => {
var _a;
return (_a = savedCallbackRef.current) == null ? void 0 : _a.call(savedCallbackRef, ...args);
};
if (delay) {
const id = setInterval(handler, delay);
return () => clearInterval(id);
}
}, [delay]);
};
// src/useIsClient/useIsClient.ts
import { useEffect as useEffect6, useState as useState4 } from "react";
var useIsClient = () => {
const [isClient, setClient] = useState4(false);
useEffect6(() => {
setClient(true);
}, []);
return isClient;
};
// src/useIsFirstRender/useIsFirstRender.ts
import { useRef as useRef5 } from "react";
var useIsFirstRender = () => {
const isFirst = useRef5(true);
if (isFirst.current) {
isFirst.current = false;
return true;
}
return isFirst.current;
};
// src/useIsMounted/useIsMounted.ts
import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef6 } from "react";
var useIsMounted = () => {
const isMounted = useRef6(false);
useEffect7(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
return useCallback4(() => isMounted.current, []);
};
// src/useIsScrolled/useIsScrolled.ts
import { useEffect as useEffect8, useState as useState5 } from "react";
var useIsScrolled = (threshold = 50) => {
const [isScrolled, setIsScrolled] = useState5(false);
useEffect8(() => {
const handleScroll = () => {
const scrolled = window.scrollY > threshold;
setIsScrolled(scrolled);
};
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, [threshold]);
return isScrolled;
};
// src/useKeyPress/useKeypress.ts
import { useEffect as useEffect9 } from "react";
var useKeyPress = ({
useAltKey = true,
active = true,
targetCode,
onPress,
dep = []
}) => {
useEffect9(() => {
const targetCodesArray = Array.isArray(targetCode) ? targetCode : [targetCode];
const downHandler = ({ code, altKey }) => {
if (altKey === useAltKey && targetCodesArray.includes(code) && active) {
onPress();
}
};
window.addEventListener("keydown", downHandler);
return () => {
window.removeEventListener("keydown", downHandler);
};
}, [active, useAltKey, onPress, targetCode, ...dep]);
};
// src/useLatestValue/useLatestValue.ts
import { useRef as useRef7 } from "react";
var useLatestValue = (value) => {
const cache = useRef7(value);
useIsomorphicLayoutEffect(() => {
cache.current = value;
}, [value]);
return cache;
};
// src/useLockScroll/useLockScroll.ts
var useLockScroll = (isOpen = true) => {
useIsomorphicLayoutEffect(() => {
if (isOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "unset";
}
return () => {
document.body.style.overflow = "unset";
};
}, [isOpen]);
};
// src/useMount/useMount.ts
import { useEffect as useEffect10 } from "react";
var useMount = (callback) => {
useEffect10(callback, []);
};
// src/useOnClickOutside/useOnClickOutside.ts
import { useEffect as useEffect11 } from "react";
var useOnClickOutside = (ref, handler) => {
useEffect11(() => {
const listener = (event) => {
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
};
}, [ref, handler]);
};
// src/useOnlineStatus/useOnlineStatus.ts
import { useSyncExternalStore } from "react";
var getNetworkState = () => {
return navigator.onLine;
};
var subscribe = (callback) => {
window.addEventListener("online", callback);
window.addEventListener("offline", callback);
return () => {
window.removeEventListener("online", callback);
window.removeEventListener("offline", callback);
};
};
var getServerSnapshot = () => {
return true;
};
var useOnlineStatus = () => {
const isOnline = useSyncExternalStore(
subscribe,
getNetworkState,
getServerSnapshot
);
return isOnline;
};
// src/useOverlay/OverlayContext.tsx
import {
useContext,
useState as useState6,
useMemo,
useCallback as useCallback5,
createContext,
Fragment
} from "react";
import { jsx, jsxs } from "react/jsx-runtime";
var OverlayContext = createContext(null);
var OverlayProvider = ({ children }) => {
const [overlays, setOverlays] = useState6(/* @__PURE__ */ new Map());
const openOverlay = useCallback5((id, element) => {
setOverlays((prev) => {
const cloned = new Map(prev);
cloned.set(id, element);
return cloned;
});
}, []);
const closeOverlay = useCallback5((id) => {
setOverlays((prev) => {
const cloned = new Map(prev);
cloned.delete(id);
return cloned;
});
}, []);
const contextValue = useMemo(
() => ({ openOverlay, closeOverlay }),
[openOverlay, closeOverlay]
);
return /* @__PURE__ */ jsxs(OverlayContext.Provider, { value: contextValue, children: [
children,
/* @__PURE__ */ jsx("div", { id: "overlay-container", children: [...overlays.entries()].map(([key, element]) => /* @__PURE__ */ jsx(Fragment, { children: element }, key)) })
] });
};
var useOverlayContext = () => {
const ctx = useContext(OverlayContext);
if (!ctx)
throw new Error(
"Cannot find OverlayContext. It should be wrapped within OverlayProvider."
);
return ctx;
};
// src/useOverlay/useOverlay.ts
import { useState as useState7, useCallback as useCallback6 } from "react";
var overlayId = 1;
var useOverlay = () => {
const { openOverlay, closeOverlay } = useOverlayContext();
const [id] = useState7(() => String(overlayId++));
const open = useCallback6(
(element) => openOverlay(id, element),
[openOverlay, id]
);
const close = useCallback6(() => closeOverlay(id), [closeOverlay, id]);
return { open, close };
};
// src/useSafeContext/useSafeContext.ts
import { useContext as useContext2 } from "react";
var useSafeContext = (unsafeContext, customMessage) => {
const context = useContext2(unsafeContext);
if (!context) {
const displayName = unsafeContext.displayName;
let errorMessage;
switch (typeof customMessage) {
case "string":
errorMessage = customMessage;
break;
case "function":
errorMessage = customMessage(displayName);
break;
default: {
const contextName = displayName || "a context";
errorMessage = `You are trying to use ${contextName} outside of the provider`;
break;
}
}
throw new Error(errorMessage);
}
return context;
};
// src/useToggle/useToggle.ts
import { useCallback as useCallback7, useState as useState8 } from "react";
var useToggle = (defaultValue) => {
const [value, setValue] = useState8(!!defaultValue);
const toggle = useCallback7(() => setValue((prev) => !prev), []);
return [value, toggle, setValue];
};
// src/useUpdateEffect/useUpdateEffect.ts
import { useEffect as useEffect12 } from "react";
var useUpdateEffect = (effect, deps) => {
const isFirst = useIsFirstRender();
useEffect12(() => {
if (!isFirst) {
return effect();
}
}, deps);
};
// src/useUpload/useUpload.ts
import { useState as useState9 } from "react";
var useUpload = () => {
const [file, setFile] = useState9();
const upload = () => {
const promise = new Promise((resolve, reject) => {
const input = document.createElement("input");
input.accept = "image/*";
const timeout = setTimeout(reject, 1e3 * 60 * 3);
input.type = "file";
input.onchange = () => {
clearTimeout(timeout);
if (!input.files) {
return reject();
}
const file2 = input.files[0];
setFile(file2);
resolve(file2);
};
input.click();
});
return promise;
};
return { upload, file };
};
// src/useWindowSize/useWindowSize.ts
import { useEffect as useEffect13, useState as useState10 } from "react";
var useWindowSize = () => {
const [windowSize, setWindowSize] = useState10();
useEffect13(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener("resize", handleResize);
handleResize();
return () => window.removeEventListener("resize", handleResize);
}, []);
return windowSize;
};
export {
OverlayProvider,
canUseDOM,
useBodyScrollLock,
useBoolean,
useCallbackOnce,
useCopyToClipboard,
useDebounce,
useDocumentTitle,
useEventListener,
useFetch,
useForceUpdate,
useInterval,
useIsClient,
useIsFirstRender,
useIsMounted,
useIsScrolled,
useIsomorphicLayoutEffect,
useKeyPress,
useLatestValue,
useLockScroll,
useMount,
useOnClickOutside,
useOnlineStatus,
useOverlay,
useOverlayContext,
useSafeContext,
useToggle,
useUpdateEffect,
useUpload,
useWindowSize
};