UNPKG

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
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 };