react-reuse-hooks
Version:
A collection of 30+ production-ready reusable React hooks for web apps, covering state, effects, media, forms, and utilities.
482 lines (382 loc) • 12.3 kB
JavaScript
import { useState, useEffect, useRef, useReducer, useLayoutEffect } from 'react';
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (err) {
return initialValue;
}
});
useEffect(() => {
try {
localStorage.setItem(key, JSON.stringify(storedValue));
} catch (err) {
console.error(err);
}
}, [key, storedValue]);
return [storedValue, setStoredValue];
}
function useDebounce(value, delay = 500) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debounced;
}
function useToggle(initialValue = false) {
const [state, setState] = useState(initialValue);
const toggle = () => setState((prev) => !prev);
return [state, toggle];
}
function useClickOutside(ref, callback) {
useEffect(() => {
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
callback();
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [ref, callback]);
}
function useClipboard() {
const [copied, setCopied] = useState(false);
const copy = async (text) => {
try {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
} catch (err) {
console.error("Failed to copy!", err);
}
};
return [copied, copy];
}
function useCookie(name, initialValue) {
const getCookie = () => {
const matches = document.cookie.match(
new RegExp(
`(?:^|; )${name.replace(/([\.$?*|{}()\[\]\\\/\+^])/g, "\\$1")}=([^;]*)`
)
);
return matches ? decodeURIComponent(matches[1]) : initialValue;
};
const [cookie, setCookieState] = useState(getCookie);
const setCookie = (value, days) => {
const expires = days
? `; expires=${new Date(Date.now() + days * 864e5).toUTCString()}`
: "";
document.cookie = `${name}=${encodeURIComponent(
value || ""
)}${expires}; path=/`;
setCookieState(value);
};
return [cookie, setCookie];
}
function useDarkMode() {
const [isDarkMode, setIsDarkMode] = useState(() => {
return localStorage.getItem("theme") === "dark";
});
useEffect(() => {
const className = "dark";
const bodyClass = window.document.body.classList;
isDarkMode ? bodyClass.add(className) : bodyClass.remove(className);
localStorage.setItem("theme", isDarkMode ? "dark" : "light");
}, [isDarkMode]);
return [isDarkMode, () => setIsDarkMode((prev) => !prev)];
}
function useDeviceOrientation() {
const [orientation, setOrientation] = useState({
alpha: null,
beta: null,
gamma: null,
});
useEffect(() => {
const handler = (event) => {
setOrientation({
alpha: event.alpha,
beta: event.beta,
gamma: event.gamma,
});
};
window.addEventListener("deviceorientation", handler);
return () => window.removeEventListener("deviceorientation", handler);
}, []);
return orientation;
}
function useFocus() {
const [focused, setFocused] = useState(false);
const ref = useRef();
useEffect(() => {
const node = ref.current;
if (!node) return;
const onFocus = () => setFocused(true);
const onBlur = () => setFocused(false);
node.addEventListener("focus", onFocus);
node.addEventListener("blur", onBlur);
return () => {
node.removeEventListener("focus", onFocus);
node.removeEventListener("blur", onBlur);
};
}, [ref]);
return [ref, focused];
}
function useForceUpdate() {
const [, forceUpdate] = useReducer((x) => x + 1, 0);
return forceUpdate;
}
function useElementSize() {
const ref = useRef();
const [size, setSize] = useState({ width: 0, height: 0 });
useLayoutEffect(() => {
const updateSize = () => {
if (ref.current) {
setSize({
width: ref.current.offsetWidth,
height: ref.current.offsetHeight,
});
}
};
updateSize();
window.addEventListener("resize", updateSize);
return () => window.removeEventListener("resize", updateSize);
}, []);
return [ref, size];
}
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
const handleResize = () =>
setSize({ width: window.innerWidth, height: window.innerHeight });
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return size;
}
function useGeolocation() {
const [position, setPosition] = useState({ latitude: null, longitude: null });
useEffect(() => {
if (!navigator.geolocation) return;
const watcher = navigator.geolocation.watchPosition(
(pos) =>
setPosition({
latitude: pos.coords.latitude,
longitude: pos.coords.longitude,
}),
(err) => console.error(err)
);
return () => navigator.geolocation.clearWatch(watcher);
}, []);
return position;
}
function useHover() {
const [hovered, setHovered] = useState(false);
const ref = useRef();
useEffect(() => {
const node = ref.current;
if (!node) return;
const handleMouseEnter = () => setHovered(true);
const handleMouseLeave = () => setHovered(false);
node.addEventListener("mouseenter", handleMouseEnter);
node.addEventListener("mouseleave", handleMouseLeave);
return () => {
node.removeEventListener("mouseenter", handleMouseEnter);
node.removeEventListener("mouseleave", handleMouseLeave);
};
}, [ref]);
return [ref, hovered];
}
function useInput(initialValue) {
const [value, setValue] = useState(initialValue);
const onChange = (e) => setValue(e.target.value);
return {
value,
onChange,
reset: () => setValue(initialValue),
};
}
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
return () => {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
};
}, []);
return isOnline;
}
function useThrottle(value, limit) {
const [throttledValue, setThrottledValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setThrottledValue(value), limit);
return () => clearTimeout(handler);
}, [value, limit]);
return throttledValue;
}
function useCopyToClipboard() {
const [success, setSuccess] = useState(false);
const copy = async (text) => {
try {
await navigator.clipboard.writeText(text);
setSuccess(true);
setTimeout(() => setSuccess(false), 2000);
} catch (err) {
setSuccess(false);
}
};
return [success, copy];
}
function useKeyPress(targetKey) {
const [keyPressed, setKeyPressed] = useState(false);
function downHandler({ key }) {
if (key === targetKey) {
setKeyPressed(true);
}
}
function upHandler({ key }) {
if (key === targetKey) {
setKeyPressed(false);
}
}
useEffect(() => {
window.addEventListener("keydown", downHandler);
window.addEventListener("keyup", upHandler);
return () => {
window.removeEventListener("keydown", downHandler);
window.removeEventListener("keyup", upHandler);
};
}, []);
return keyPressed;
}
function useScrollPosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const updatePosition = () => {
setPosition({ x: window.scrollX, y: window.scrollY });
};
window.addEventListener("scroll", updatePosition);
updatePosition();
return () => window.removeEventListener("scroll", updatePosition);
}, []);
return position;
}
function useEventListener(eventName, handler, element = window) {
const savedHandler = useRef();
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(() => {
const isSupported = element && element.addEventListener;
if (!isSupported) return;
const eventListener = (event) => savedHandler.current(event);
element.addEventListener(eventName, eventListener);
return () => {
element.removeEventListener(eventName, eventListener);
};
}, [eventName, element]);
}
function useWhyDidYouUpdate(name, props) {
const previousProps = useRef();
useEffect(() => {
if (previousProps.current) {
const changedProps = Object.entries(props).reduce(
(diff, [key, value]) => {
if (previousProps.current[key] !== value) {
diff[key] = {
from: previousProps.current[key],
to: value,
};
}
return diff;
},
{}
);
if (Object.keys(changedProps).length > 0) {
console.log("[why-did-you-update]", name, changedProps);
}
}
previousProps.current = props;
});
}
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
function useOnScreen(options) {
const ref = useRef();
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
setIsVisible(entry.isIntersecting);
}, options);
if (ref.current) observer.observe(ref.current);
return () => {
if (ref.current) observer.unobserve(ref.current);
};
}, [ref, options]);
return [ref, isVisible];
}
function useStateWithCallback(initialState) {
const [state, setState] = useState(initialState);
const callbackRef = useRef(null);
const updateState = (newState, callback) => {
callbackRef.current = callback;
setState(newState);
};
useEffect(() => {
if (callbackRef.current) {
callbackRef.current(state);
callbackRef.current = null;
}
}, [state]);
return [state, updateState];
}
function useSessionStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = sessionStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
const setValue = (value) => {
setStoredValue(value);
sessionStorage.setItem(key, JSON.stringify(value));
};
return [storedValue, setValue];
}
function useTimeout(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay === null) return;
const id = setTimeout(() => savedCallback.current(), delay);
return () => clearTimeout(id);
}, [delay]);
}
function useUpdateEffect(effect, deps) {
const isInitialMount = useRef(true);
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
} else {
return effect();
}
}, deps);
}
export { useClickOutside, useClipboard, useCookie, useCopyToClipboard, useDarkMode, useDebounce, useDeviceOrientation, useElementSize, useEventListener, useFocus, useForceUpdate, useGeolocation, useHover, useInput, useKeyPress, useLocalStorage, useOnScreen, useOnlineStatus, usePrevious, useScrollPosition, useSessionStorage, useStateWithCallback, useThrottle, useTimeout, useToggle, useUpdateEffect, useWhyDidYouUpdate, useWindowSize };