How would you re-render a component when the window is resized
Subscribe to the window resize event in a useEffect, store the dimension in state so a change triggers a re-render, and clean up the listener on unmount. Throttle/debounce the handler for performance. Extract it into a reusable useWindowSize hook; consider ResizeObserver for element-level sizing.
React doesn't re-render on window resize automatically — window.innerWidth isn't reactive. You make it reactive by putting the value in state and updating it from a resize listener.
The hook
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); // cleanup!
// empty deps — set up once
}, []);
return size;
}Using it:
const { width } = useWindowSize();
return width < 768 ? <MobileNav /> : <DesktopNav />;State change → re-render. Done.
The three things people get wrong
- No cleanup — not removing the listener in the effect's return leaks listeners on every mount/unmount.
- No throttle/debounce —
resizefires rapidly (dozens of events per second while dragging). Setting state on each one floods React with renders. Throttle (e.g. every ~100–150ms — keeps it responsive) or debounce (only after resize stops — cheaper) the handler.
``js const handleResize = throttle(() => setSize({...}), 150); // and: return () => { handleResize.cancel?.(); window.removeEventListener(...); } ``
- SSR —
windowdoesn't exist on the server. Guard the initializer, or initialize to a default and set the real value inuseEffect(which only runs client-side). Beware hydration mismatch.
Better alternatives — often you don't need JS at all
- CSS media queries / container queries — if you just need layout to change, do it in CSS. No re-render, no JS. This is the right answer most of the time.
window.matchMedia— if you need a boolean breakpoint in JS (isMobile), listen to aMediaQueryListinstead of raw resize — it only fires when the breakpoint actually crosses, not on every pixel.ResizeObserver— if you care about a specific element's size (not the window), this is the correct tool — it's element-scoped and more efficient.
How to answer
"Window dimensions aren't reactive, so I put them in state and update from a throttled resize listener inside a useEffect, with cleanup on unmount — packaged as a useWindowSize hook. But I'd first ask whether I even need JS: if it's purely layout, CSS media/container queries are better; if I need a JS boolean breakpoint, matchMedia; if I need an element's size, ResizeObserver."
Follow-up questions
- •Why throttle or debounce the resize handler?
- •When should you use CSS media queries instead of a resize listener?
- •What's the difference between matchMedia and listening to raw resize?
- •When is ResizeObserver the right tool instead?
- •How do you handle window not existing during SSR?
Common mistakes
- •Forgetting to remove the listener on unmount — listener leak.
- •Setting state on every resize event with no throttle/debounce.
- •Reading window.innerWidth directly and expecting re-renders.
- •Accessing window during SSR without a guard.
- •Using JS for something CSS media/container queries handle.
Performance considerations
- •resize fires very frequently — unthrottled state updates cause render storms. Throttle/debounce the handler. matchMedia fires only on breakpoint crossings, far cheaper. CSS media/container queries avoid JS and re-renders entirely.
Edge cases
- •SSR / hydration mismatch when window isn't available server-side.
- •Orientation change on mobile.
- •Rapid continuous resizing (window drag).
- •Element resizing independently of the window (use ResizeObserver).
Real-world examples
- •A useWindowSize / useMediaQuery hook switching between mobile and desktop navigation.
- •ResizeObserver driving a chart that must re-fit its container.