DEV Community

Cover image for 30 Tiny React Hooks That Punch Way Above Their Weight
Mursal Furqan Kumbhar
Mursal Furqan Kumbhar

Posted on

30 Tiny React Hooks That Punch Way Above Their Weight

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;
};
Enter fullscreen mode Exit fullscreen mode

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]);
};
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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];
};
Enter fullscreen mode Exit fullscreen mode

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 };
};
Enter fullscreen mode Exit fullscreen mode

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];
};
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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" });
Enter fullscreen mode Exit fullscreen mode

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]);
};
Enter fullscreen mode Exit fullscreen mode

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]);
};
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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 };
};
Enter fullscreen mode Exit fullscreen mode

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];
};
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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]);
};
Enter fullscreen mode Exit fullscreen mode

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]);
};
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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];
};
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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];
};
Enter fullscreen mode Exit fullscreen mode

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);
};
Enter fullscreen mode Exit fullscreen mode

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, []);
};
Enter fullscreen mode Exit fullscreen mode

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 };
};
Enter fullscreen mode Exit fullscreen mode

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 };
};
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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];
};
Enter fullscreen mode Exit fullscreen mode

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]);
};
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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.

happycoding

Top comments (0)