ResizeObserver Loop Limit Exceeded Warning
Fix ResizeObserver loop completed with undelivered notifications, find the feedback loop between observation and layout writes, and break it with rAF or guards.
Symptoms
- Console warning: ResizeObserver loop limit exceeded or loop completed with undelivered notifications
- The page jitters: an element visibly oscillates in size in a rapid cycle
- The warning correlates with components that size themselves from observed content
Possible causes
- A resize callback writes styles that change the observed element own size, triggering the observer again
- Two elements observe and resize each other in a cycle: A grows, B reacts, A reacts to B
- The callback synchronously reads layout and writes back within the same observation pass
- A library sizes a container to its content while observing the same container for changes
Diagnosis steps
Reproduce and watch the oscillation
DevTools → Elements → toggle the component; watch computed width flickerInterpretation
Visible size oscillation confirms a feedback loop rather than a benign one-off notification backlog.
Audit the callback writes
grep -Rn -A6 "ResizeObserver" src/ | grep -E "style\.|width|height" | headInterpretation
Every style write inside a resize callback on the observed element (or its mutual partner) is a loop edge.
Add a change guard
instrument the callback: log entry sizes across two invocationsInterpretation
Identical or alternating sizes across invocations is the loop signature; converging sizes are healthy settling.
Root cause
ResizeObserver callbacks run in the same frame after layout. If a callback writes styles that invalidate the layout of an observed element, the browser schedules another observation pass; after a few iterations of this feedback, it gives up with the loop-limit warning to protect the frame budget.
Fix
Break the cycle: defer writes with requestAnimationFrame, guard against no-op writes (only apply when the value actually changes), size from content without observing the same element (or observe the inner content), and avoid mutual observation pairs that write to each other.
Risk note
Suppressing the warning globally (window.onerror filter) hides genuine infinite layout loops from every future component. Fix the specific loop.
Verify the fix
After the fix, resizing interactions produce no warning, element size converges in one step, and frame rate stays flat during continuous resize.
Symptom walkthrough
The warning text explains its own mercy:
ResizeObserver loop limit exceeded
The browser ran several observation passes in one frame because each pass invalidated the layout again, then stopped iterating to protect the frame. The visual twin is jitter: an element oscillating between two sizes as A reacts to B and B reacts back. The canonical culprit shape:
const ro = new ResizeObserver(([entry]) => {
el.style.height = entry.contentRect.height * 2 + 'px';
// the write changes the observed size -> next observation -> loop
});
ro.observe(el);
Fix in practice
Three techniques, often combined. Defer the write out of the observation pass:
const ro = new ResizeObserver(([entry]) => {
requestAnimationFrame(() => {
el.style.height = entry.contentRect.height * 2 + 'px';
});
});
Guard against no-op writes so convergence ends the loop:
let last = 0;
const ro = new ResizeObserver(([entry]) => {
const h = Math.round(entry.contentRect.height);
if (h === last) return; // converged: stop reacting
last = h;
el.style.height = h * 2 + 'px';
});
And restructure self-sizing: observe the inner content, size the outer wrapper — never observe the element whose style you are writing.