Hello devs 👋
Ever notice how the same ten lines of React keep showing up in every project you touch? You copy them from some old repo, paste them into the new one, rename a variable or two, and quietly pretend you wrote them fresh. Be honest, we all do it. Half of frontend work is just remembering which folder you left that one perfect hook in.
So I stopped pretending and collected the ones I actually reach for. This article is a stash of 30 tiny, battle tested React snippets you can drop straight into your codebase. No extra libraries, no bloat, no npm install marathon. Just copy, paste, and ship.
Bookmark this one. Future you is going to be very grateful to present you.
1. useDebounce
const useDebounce = (value, delay = 500) => {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const t = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(t);
}, [value, delay]);
return debounced;
};
Delays rapid state updates so you are not firing on every keystroke. Perfect for search bars and typeahead inputs where you only want to hit the API once the user stops typing. Your backend will send you a thank you card.
2. useClickOutside
const useClickOutside = (ref, handler) => {
useEffect(() => {
const listener = (e) => {
if (!ref.current || ref.current.contains(e.target)) return;
handler();
};
document.addEventListener("mousedown", listener);
return () => document.removeEventListener("mousedown", listener);
}, [ref, handler]);
};
Closes a modal, dropdown, or popover when the user clicks anywhere outside of it. Attach the ref to your menu, pass a close function as the handler, and you are done. Ten lines that quietly power half the dropdowns on the internet.
3. usePrevious
const usePrevious = (value) => {
const ref = useRef();
useEffect(() => { ref.current = value; }, [value]);
return ref.current;
};
Gives you the value a prop or state had on the last render. Handy when you want to compare old versus new, like triggering an animation only when a count actually goes up. Tiny hook, surprisingly clutch.
4. useLocalStorage
const useLocalStorage = (key, initial) => {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initial;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
};
Works exactly like useState but survives a page refresh. Great for theme choices, draft form data, or a dismissed banner that should stay dismissed. Same API you already know, zero new mental overhead.
5. useFetch
const useFetch = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData)
.finally(() => setLoading(false));
}, [url]);
return { data, loading };
};
A pocket sized React Query. Fetches data and tracks the loading state in under fifteen lines. Not built for retries or caching, but it covers a solid 90 percent of simple screens.
6. useToggle
const useToggle = (initial = false) => {
const [value, setValue] = useState(initial);
const toggle = useCallback(() => setValue((v) => !v), []);
return [value, toggle];
};
Stop writing setOpen(prev => !prev) for the hundredth time. This gives you a boolean and a one word toggle function. Modals, sidebars, accordions, password visibility, all of it gets cleaner.
7. useIsMobile
const useIsMobile = (breakpoint = 768) => {
const [isMobile, setIsMobile] = useState(window.innerWidth <= breakpoint);
useEffect(() => {
const onResize = () => setIsMobile(window.innerWidth <= breakpoint);
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, [breakpoint]);
return isMobile;
};
Tells you whether the user is on a phone or a bigger screen. Use it to swap a desktop sidebar for a hamburger menu, or to render a completely different layout. CSS handles most of this, but sometimes you need the answer in JavaScript.
8. scrollToTop
const scrollToTop = () =>
window.scrollTo({ top: 0, behavior: "smooth" });
Smoothly glides the page back to the top. Wire it up to a floating "back to top" button on long blog posts or endless product lists. One line, instant polish.
9. useInterval
const useInterval = (callback, delay) => {
const savedCallback = useRef(callback);
useEffect(() => { savedCallback.current = callback; }, [callback]);
useEffect(() => {
if (delay === null) return;
const id = setInterval(() => savedCallback.current(), delay);
return () => clearInterval(id);
}, [delay]);
};
Runs a function on a repeating timer the React way. The saved ref means your callback always sees fresh state instead of a stale closure, which is the classic setInterval trap. Reach for it on live clocks, auto refreshing dashboards, and carousels that slide on their own.
10. useTimeout
const useTimeout = (callback, delay) => {
const savedCallback = useRef(callback);
useEffect(() => { savedCallback.current = callback; }, [callback]);
useEffect(() => {
if (delay === null) return;
const id = setTimeout(() => savedCallback.current(), delay);
return () => clearTimeout(id);
}, [delay]);
};
The one shot cousin of useInterval. Fires your callback once after a delay and cleans itself up properly on unmount. Nice for auto hiding a toast or showing a "still loading" message after a few seconds.
11. useWindowSize
const useWindowSize = () => {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
const onResize = () =>
setSize({ width: window.innerWidth, height: window.innerHeight });
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
return size;
};
Gives you live width and height that update as the window resizes. Useful for canvas rendering, virtualized lists, or any chart that needs exact pixel dimensions. The component re renders the moment the viewport changes.
12. useMediaQuery
const useMediaQuery = (query) => {
const [matches, setMatches] = useState(
() => window.matchMedia(query).matches
);
useEffect(() => {
const mql = window.matchMedia(query);
const onChange = () => setMatches(mql.matches);
mql.addEventListener("change", onChange);
return () => mql.removeEventListener("change", onChange);
}, [query]);
return matches;
};
Your CSS media queries, now available in JavaScript. Pass something like "(min-width: 1024px)" and get a boolean back. Cleaner than a manual resize listener when you only care about a specific breakpoint.
13. useCopyToClipboard
const useCopyToClipboard = () => {
const [copied, setCopied] = useState(false);
const copy = async (text) => {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return { copied, copy };
};
Copies any text and flips a "copied" flag for two seconds so you can show a little checkmark. Every code block, share link, and API key field wants this. Small touch, big "wow this app feels finished" energy.
14. useHover
const useHover = () => {
const ref = useRef(null);
const [hovered, setHovered] = useState(false);
useEffect(() => {
const node = ref.current;
if (!node) return;
const enter = () => setHovered(true);
const leave = () => setHovered(false);
node.addEventListener("mouseenter", enter);
node.addEventListener("mouseleave", leave);
return () => {
node.removeEventListener("mouseenter", enter);
node.removeEventListener("mouseleave", leave);
};
}, []);
return [ref, hovered];
};
Tracks hover state without sprinkling onMouseEnter and onMouseLeave everywhere. Attach the ref, read the boolean, render a tooltip or a preview card. Great when hover needs to drive logic and not just a CSS color change.
15. useOnlineStatus
const useOnlineStatus = () => {
const [online, setOnline] = useState(navigator.onLine);
useEffect(() => {
const goOnline = () => setOnline(true);
const goOffline = () => setOnline(false);
window.addEventListener("online", goOnline);
window.addEventListener("offline", goOffline);
return () => {
window.removeEventListener("online", goOnline);
window.removeEventListener("offline", goOffline);
};
}, []);
return online;
};
Tells you in real time whether the user just lost their connection. Show a "you are offline" banner, pause auto saves, or queue requests for later. Essential for anything that pretends to be a serious web app.
16. useEventListener
const useEventListener = (event, handler, element = window) => {
const savedHandler = useRef(handler);
useEffect(() => { savedHandler.current = handler; }, [handler]);
useEffect(() => {
const listener = (e) => savedHandler.current(e);
element.addEventListener(event, listener);
return () => element.removeEventListener(event, listener);
}, [event, element]);
};
One hook to rule all your addEventListener boilerplate. It handles the subscribe and cleanup dance for you and always calls the latest handler. Build most of the other hooks in this list on top of it if you want to get fancy.
17. useDocumentTitle
const useDocumentTitle = (title) => {
useEffect(() => {
const previous = document.title;
document.title = title;
return () => { document.title = previous; };
}, [title]);
};
Updates the browser tab title and politely restores the old one when the component unmounts. Perfect for per page titles in a single page app, or showing an unread count like "(3) Inbox". Tiny SEO and UX win in three lines.
18. useKeyPress
const useKeyPress = (targetKey) => {
const [pressed, setPressed] = useState(false);
useEffect(() => {
const down = (e) => e.key === targetKey && setPressed(true);
const up = (e) => e.key === targetKey && setPressed(false);
window.addEventListener("keydown", down);
window.addEventListener("keyup", up);
return () => {
window.removeEventListener("keydown", down);
window.removeEventListener("keyup", up);
};
}, [targetKey]);
return pressed;
};
Returns true while a given key is held down. Wire up Escape to close a modal, Enter to submit, or arrow keys to move through a list. Keyboard shortcuts make your app feel like it was built by people who actually use it.
19. useDarkMode
const useDarkMode = () => {
const [dark, setDark] = useState(
() => localStorage.getItem("theme") === "dark"
);
useEffect(() => {
document.documentElement.classList.toggle("dark", dark);
localStorage.setItem("theme", dark ? "dark" : "light");
}, [dark]);
return [dark, setDark];
};
A complete dark mode toggle that remembers the choice across reloads. It flips a class on the html element so Tailwind or your CSS variables do the rest. Ship the feature everyone immediately looks for in settings.
20. useThrottle
const useThrottle = (value, limit = 500) => {
const [throttled, setThrottled] = useState(value);
const lastRan = useRef(Date.now());
useEffect(() => {
const handler = setTimeout(() => {
if (Date.now() - lastRan.current >= limit) {
setThrottled(value);
lastRan.current = Date.now();
}
}, limit - (Date.now() - lastRan.current));
return () => clearTimeout(handler);
}, [value, limit]);
return throttled;
};
Debounce waits for things to stop, throttle keeps a steady pace. This caps how often a value updates, which is exactly what you want for scroll position, mouse tracking, or window resize math. Smooth performance without melting the main thread.
21. useIntersectionObserver
const useIntersectionObserver = (options) => {
const ref = useRef(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => setIsVisible(entry.isIntersecting),
options
);
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, [options]);
return [ref, isVisible];
};
Tells you when an element scrolls into view. This is the engine behind lazy loaded images, fade in on scroll animations, and infinite feeds that load more as you reach the bottom. Way cheaper than listening to the scroll event yourself.
22. useUpdateEffect
const useUpdateEffect = (effect, deps) => {
const isFirst = useRef(true);
useEffect(() => {
if (isFirst.current) {
isFirst.current = false;
return;
}
return effect();
}, deps);
};
Like useEffect, but it skips the very first render and only runs on updates. Saves you from that annoying duplicate API call or analytics event that fires the moment a component mounts. The "only when something actually changes" hook.
23. useEffectOnce
const useEffectOnce = (effect) => {
useEffect(effect, []);
};
A tiny wrapper that runs an effect exactly once on mount. Mostly it just signals your intent clearly so the next dev does not stare at an empty dependency array wondering if you forgot something. Readability counts.
24. useCounter
const useCounter = (initial = 0, step = 1) => {
const [count, setCount] = useState(initial);
const increment = () => setCount((c) => c + step);
const decrement = () => setCount((c) => c - step);
const reset = () => setCount(initial);
return { count, increment, decrement, reset };
};
A ready made counter with increment, decrement, and reset baked in. Drop it into quantity selectors, pagination, or step by step wizards. Less wiring, fewer bugs, more time for the fun stuff.
25. useArray
const useArray = (initial = []) => {
const [array, setArray] = useState(initial);
const push = (item) => setArray((a) => [...a, item]);
const remove = (i) => setArray((a) => a.filter((_, idx) => idx !== i));
const clear = () => setArray([]);
return { array, set: setArray, push, remove, clear };
};
Managing a list in state with clean helper methods instead of spread syntax soup. Add, remove by index, or wipe it clean without rewriting the same map and filter logic. Great for to do lists, tag inputs, and cart items.
26. useGeolocation
const useGeolocation = () => {
const [coords, setCoords] = useState(null);
useEffect(() => {
const id = navigator.geolocation.watchPosition((pos) =>
setCoords({
lat: pos.coords.latitude,
lng: pos.coords.longitude,
})
);
return () => navigator.geolocation.clearWatch(id);
}, []);
return coords;
};
Watches the user location and updates as they move. Use it for "stores near me", live delivery tracking, or auto filling a city field. Just remember the browser will ask for permission first, so handle the null case gracefully.
27. useIdle
const useIdle = (timeout = 3000) => {
const [idle, setIdle] = useState(false);
useEffect(() => {
let timer = setTimeout(() => setIdle(true), timeout);
const reset = () => {
clearTimeout(timer);
setIdle(false);
timer = setTimeout(() => setIdle(true), timeout);
};
window.addEventListener("mousemove", reset);
window.addEventListener("keydown", reset);
return () => {
clearTimeout(timer);
window.removeEventListener("mousemove", reset);
window.removeEventListener("keydown", reset);
};
}, [timeout]);
return idle;
};
Detects when the user has gone quiet for a while. Pause a video, dim the screen, or auto log out of a banking dashboard after inactivity. Any reset of mouse or keyboard activity wakes it back up.
28. useSessionStorage
const useSessionStorage = (key, initial) => {
const [value, setValue] = useState(() => {
const stored = sessionStorage.getItem(key);
return stored ? JSON.parse(stored) : initial;
});
useEffect(() => {
sessionStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
};
Same idea as useLocalStorage, but the data clears when the tab closes. Ideal for a multi step checkout or a wizard where you want progress saved during the visit but gone afterward. Privacy friendly persistence.
29. useScrollLock
const useScrollLock = (locked) => {
useEffect(() => {
document.body.style.overflow = locked ? "hidden" : "";
return () => { document.body.style.overflow = ""; };
}, [locked]);
};
Freezes background scrolling while a modal or drawer is open. We have all rage scrolled a popup and watched the page behind it slide around. This one line fix kills that jank and restores scroll on close.
30. useRenderCount
const useRenderCount = () => {
const count = useRef(0);
count.current += 1;
return count.current;
};
Counts how many times a component has rendered. Pure debugging gold when you are hunting down why something re renders forty times when you clicked once. Pop it in, log the number, find the culprit, take it out.
Conclusion
And that is the stash. Thirty little snippets that save you from rewriting the same logic in every single project from now until forever. Copy what you need, tweak the ones you love, and quietly become the teammate whose pull requests are weirdly clean.
I will be back soon with another round, probably something on performance or the patterns nobody warns you about until it is too late. Until then, go build something cool and stop reinventing the debounce.
Top comments (0)