@hyunjin/hooks
Version:
collection of useful hooks
585 lines (555 loc) • 17.7 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
OverlayProvider: () => OverlayProvider,
canUseDOM: () => canUseDOM,
useBodyScrollLock: () => useBodyScrollLock,
useBoolean: () => useBoolean,
useCallbackOnce: () => useCallbackOnce,
useCopyToClipboard: () => useCopyToClipboard,
useDebounce: () => useDebounce,
useDocumentTitle: () => useDocumentTitle,
useEventListener: () => useEventListener,
useFetch: () => useFetch,
useForceUpdate: () => useForceUpdate,
useInterval: () => useInterval,
useIsClient: () => useIsClient,
useIsFirstRender: () => useIsFirstRender,
useIsMounted: () => useIsMounted,
useIsScrolled: () => useIsScrolled,
useIsomorphicLayoutEffect: () => useIsomorphicLayoutEffect,
useKeyPress: () => useKeyPress,
useLatestValue: () => useLatestValue,
useLockScroll: () => useLockScroll,
useMount: () => useMount,
useOnClickOutside: () => useOnClickOutside,
useOnlineStatus: () => useOnlineStatus,
useOverlay: () => useOverlay,
useOverlayContext: () => useOverlayContext,
useSafeContext: () => useSafeContext,
useToggle: () => useToggle,
useUpdateEffect: () => useUpdateEffect,
useUpload: () => useUpload,
useWindowSize: () => useWindowSize
});
module.exports = __toCommonJS(index_exports);
// src/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.ts
var import_react = require("react");
var canUseDOM = !!(typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined");
var useIsomorphicLayoutEffect = canUseDOM ? import_react.useLayoutEffect : import_react.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
var import_react2 = require("react");
var useBoolean = (defaultValue = false) => {
if (typeof defaultValue !== "boolean") {
throw new Error("defaultValue must be `true` or `false`");
}
const [value, setValue] = (0, import_react2.useState)(defaultValue);
const setTrue = (0, import_react2.useCallback)(() => {
setValue(true);
}, []);
const setFalse = (0, import_react2.useCallback)(() => {
setValue(false);
}, []);
const toggle = (0, import_react2.useCallback)(() => {
setValue((x) => !x);
}, []);
return { value, setValue, setTrue, setFalse, toggle };
};
// src/useCallbackOnce/useCallbackOnce.ts
var import_react3 = require("react");
var useCallbackOnce = (callback, deps) => {
const hasFired = (0, import_react3.useRef)(false);
const memoizedCallback = (0, import_react3.useCallback)((...args) => {
if (hasFired.current) {
return;
}
callback(...args);
hasFired.current = true;
}, deps);
return memoizedCallback;
};
// src/useCopyToClipboard/useCopyToClipboard.ts
var import_react4 = require("react");
var useCopyToClipboard = () => {
const [copiedText, setCopiedText] = (0, import_react4.useState)(null);
const copy = (0, import_react4.useCallback)(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
var import_react5 = require("react");
var useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = (0, import_react5.useState)(value);
(0, import_react5.useEffect)(() => {
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
var import_react6 = require("react");
var useEventListener = (eventName, handler, element = globalThis, options = {}) => {
const savedHandler = (0, import_react6.useRef)(handler);
(0, import_react6.useEffect)(() => {
savedHandler.current = handler;
}, [handler]);
(0, import_react6.useEffect)(() => {
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
var import_react7 = require("react");
function useFetch(url, options) {
const cache = (0, import_react7.useRef)({});
const cancelRequest = (0, import_react7.useRef)(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] = (0, import_react7.useReducer)(fetchReducer, initialState);
(0, import_react7.useEffect)(() => {
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
var import_react8 = require("react");
var updater = (num) => (num + 1) % 1e6;
var useForceUpdate = () => {
const [, forceUpdate] = (0, import_react8.useReducer)(updater, 0);
return forceUpdate;
};
// src/useInterval/useInterval.ts
var import_react9 = require("react");
var useInterval = (callback, delay) => {
const savedCallbackRef = (0, import_react9.useRef)(callback);
(0, import_react9.useEffect)(() => {
savedCallbackRef.current = callback;
}, [callback]);
(0, import_react9.useEffect)(() => {
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
var import_react10 = require("react");
var useIsClient = () => {
const [isClient, setClient] = (0, import_react10.useState)(false);
(0, import_react10.useEffect)(() => {
setClient(true);
}, []);
return isClient;
};
// src/useIsFirstRender/useIsFirstRender.ts
var import_react11 = require("react");
var useIsFirstRender = () => {
const isFirst = (0, import_react11.useRef)(true);
if (isFirst.current) {
isFirst.current = false;
return true;
}
return isFirst.current;
};
// src/useIsMounted/useIsMounted.ts
var import_react12 = require("react");
var useIsMounted = () => {
const isMounted = (0, import_react12.useRef)(false);
(0, import_react12.useEffect)(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
return (0, import_react12.useCallback)(() => isMounted.current, []);
};
// src/useIsScrolled/useIsScrolled.ts
var import_react13 = require("react");
var useIsScrolled = (threshold = 50) => {
const [isScrolled, setIsScrolled] = (0, import_react13.useState)(false);
(0, import_react13.useEffect)(() => {
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
var import_react14 = require("react");
var useKeyPress = ({
useAltKey = true,
active = true,
targetCode,
onPress,
dep = []
}) => {
(0, import_react14.useEffect)(() => {
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
var import_react15 = require("react");
var useLatestValue = (value) => {
const cache = (0, import_react15.useRef)(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
var import_react16 = require("react");
var useMount = (callback) => {
(0, import_react16.useEffect)(callback, []);
};
// src/useOnClickOutside/useOnClickOutside.ts
var import_react17 = require("react");
var useOnClickOutside = (ref, handler) => {
(0, import_react17.useEffect)(() => {
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
var import_react18 = require("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 = (0, import_react18.useSyncExternalStore)(
subscribe,
getNetworkState,
getServerSnapshot
);
return isOnline;
};
// src/useOverlay/OverlayContext.tsx
var import_react19 = require("react");
var import_jsx_runtime = require("react/jsx-runtime");
var OverlayContext = (0, import_react19.createContext)(null);
var OverlayProvider = ({ children }) => {
const [overlays, setOverlays] = (0, import_react19.useState)(/* @__PURE__ */ new Map());
const openOverlay = (0, import_react19.useCallback)((id, element) => {
setOverlays((prev) => {
const cloned = new Map(prev);
cloned.set(id, element);
return cloned;
});
}, []);
const closeOverlay = (0, import_react19.useCallback)((id) => {
setOverlays((prev) => {
const cloned = new Map(prev);
cloned.delete(id);
return cloned;
});
}, []);
const contextValue = (0, import_react19.useMemo)(
() => ({ openOverlay, closeOverlay }),
[openOverlay, closeOverlay]
);
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(OverlayContext.Provider, { value: contextValue, children: [
children,
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "overlay-container", children: [...overlays.entries()].map(([key, element]) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react19.Fragment, { children: element }, key)) })
] });
};
var useOverlayContext = () => {
const ctx = (0, import_react19.useContext)(OverlayContext);
if (!ctx)
throw new Error(
"Cannot find OverlayContext. It should be wrapped within OverlayProvider."
);
return ctx;
};
// src/useOverlay/useOverlay.ts
var import_react20 = require("react");
var overlayId = 1;
var useOverlay = () => {
const { openOverlay, closeOverlay } = useOverlayContext();
const [id] = (0, import_react20.useState)(() => String(overlayId++));
const open = (0, import_react20.useCallback)(
(element) => openOverlay(id, element),
[openOverlay, id]
);
const close = (0, import_react20.useCallback)(() => closeOverlay(id), [closeOverlay, id]);
return { open, close };
};
// src/useSafeContext/useSafeContext.ts
var import_react21 = require("react");
var useSafeContext = (unsafeContext, customMessage) => {
const context = (0, import_react21.useContext)(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
var import_react22 = require("react");
var useToggle = (defaultValue) => {
const [value, setValue] = (0, import_react22.useState)(!!defaultValue);
const toggle = (0, import_react22.useCallback)(() => setValue((prev) => !prev), []);
return [value, toggle, setValue];
};
// src/useUpdateEffect/useUpdateEffect.ts
var import_react23 = require("react");
var useUpdateEffect = (effect, deps) => {
const isFirst = useIsFirstRender();
(0, import_react23.useEffect)(() => {
if (!isFirst) {
return effect();
}
}, deps);
};
// src/useUpload/useUpload.ts
var import_react24 = require("react");
var useUpload = () => {
const [file, setFile] = (0, import_react24.useState)();
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
var import_react25 = require("react");
var useWindowSize = () => {
const [windowSize, setWindowSize] = (0, import_react25.useState)();
(0, import_react25.useEffect)(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener("resize", handleResize);
handleResize();
return () => window.removeEventListener("resize", handleResize);
}, []);
return windowSize;
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
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
});