javascript 50 lines · 8 steps

Building a throttle hook in React

A custom useThrottle hook caps how often a callback fires, then drives a scroll progress bar with it.

Explained by highlit
1import { useRef, useCallback, useEffect, useState } from "react";
2 
3function useThrottle(callback, delay) {
4 const lastRun = useRef(0);
5 const timeout = useRef(null);
6 
7 return useCallback(
8 (...args) => {
9 const now = Date.now();
10 const remaining = delay - (now - lastRun.current);
11 
12 if (remaining <= 0) {
13 if (timeout.current) {
14 clearTimeout(timeout.current);
15 timeout.current = null;
16 }
17 lastRun.current = now;
18 callback(...args);
19 } else if (!timeout.current) {
20 timeout.current = setTimeout(() => {
21 lastRun.current = Date.now();
22 timeout.current = null;
23 callback(...args);
24 }, remaining);
25 }
26 },
27 [callback, delay]
28 );
29}
30 
31export function ScrollProgressBar() {
32 const [progress, setProgress] = useState(0);
33 
34 const handleScroll = useThrottle(() => {
35 const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
36 const scrollable = scrollHeight - clientHeight;
37 setProgress(scrollable > 0 ? (scrollTop / scrollable) * 100 : 0);
38 }, 100);
39 
40 useEffect(() => {
41 window.addEventListener("scroll", handleScroll, { passive: true });
42 return () => window.removeEventListener("scroll", handleScroll);
43 }, [handleScroll]);
44 
45 return (
46 <div className="progress-track">
47 <div className="progress-fill" style={{ width: `${progress}%` }} />
48 </div>
49 );
50}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Storing timing state in refs keeps values across renders without triggering re-renders.
  2. 2Throttling with both a leading call and a trailing timeout guarantees the final event isn't dropped.
  3. 3Memoizing the returned handler lets effects add and remove the exact same listener reliably.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a throttle hook in React — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code