Skip to content

Commit 4a75768

Browse files
authored
Fix useResizeObserver loop limit exceeded warning (#2891)
debounce resizeobserver so we don't try to process more events than we can handle in a frame
1 parent 1b01e97 commit 4a75768

File tree

2 files changed

+72
-5
lines changed

2 files changed

+72
-5
lines changed

packages/@react-aria/utils/src/useResizeObserver.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {RefObject, useEffect} from 'react';
1+
import {RefObject, useEffect, useRef} from 'react';
22

33
function hasResizeObserver() {
44
return typeof window.ResizeObserver !== 'undefined';
@@ -11,6 +11,7 @@ type useResizeObserverOptionsType<T> = {
1111

1212
export function useResizeObserver<T extends Element>(options: useResizeObserverOptionsType<T>) {
1313
const {ref, onResize} = options;
14+
let raf = useRef(null);
1415

1516
useEffect(() => {
1617
let element = ref?.current;
@@ -24,17 +25,28 @@ export function useResizeObserver<T extends Element>(options: useResizeObserverO
2425
window.removeEventListener('resize', onResize, false);
2526
};
2627
} else {
27-
2828
const resizeObserverInstance = new window.ResizeObserver((entries) => {
29-
if (!entries.length) {
29+
if (raf.current) {
3030
return;
3131
}
32-
33-
onResize();
32+
// avoid Error - ResizeObserver loop limit exceeded
33+
// it's ok to use a raf, ResizeObservers are already async and now we're just debouncing on frames
34+
raf.current = window.requestAnimationFrame(() => {
35+
raf.current = null;
36+
if (!Array.isArray(entries) || !entries.length) {
37+
return;
38+
}
39+
onResize();
40+
});
3441
});
42+
3543
resizeObserverInstance.observe(element);
3644

3745
return () => {
46+
if (raf.current) {
47+
window.cancelAnimationFrame(raf.current);
48+
raf.current = null;
49+
}
3850
if (element) {
3951
resizeObserverInstance.unobserve(element);
4052
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
2+
import {Button} from '@react-spectrum/button';
3+
import {Meta, Story} from '@storybook/react';
4+
import React, {useEffect, useRef, useState} from 'react';
5+
import {useResizeObserver} from '../src';
6+
7+
export default {
8+
title: 'useResizeObserver'
9+
} as Meta;
10+
11+
const Template: Story<any> = () => (
12+
<App />
13+
);
14+
15+
export const UseResizeObserverLoopLimit = Template.bind({});
16+
UseResizeObserverLoopLimit.args = {};
17+
18+
const animalSet = ['🐰', '🦊', '🐻', '🐭', '🐼', '🐸'];
19+
function App() {
20+
let ref = useRef<HTMLDivElement>();
21+
let index = useRef(0);
22+
let [animals, setAnimals] = useState([animalSet[0]]);
23+
24+
function insertAnimal() {
25+
index.current = index.current + 1;
26+
setAnimals(prev => [...prev, animalSet[index.current % animalSet.length]]);
27+
}
28+
29+
useEffect(() => {
30+
let onError = (err) => {
31+
console.log(err);
32+
};
33+
window.addEventListener('error', onError);
34+
return () => {
35+
window.removeEventListener('error', onError);
36+
};
37+
}, []);
38+
39+
let onResize = () => {
40+
const {width} = ref.current.getBoundingClientRect();
41+
42+
ref.current.style.height = `${width}px`;
43+
};
44+
45+
useResizeObserver({onResize, ref});
46+
47+
return (
48+
<>
49+
<div ref={ref} id="red-box">
50+
{animals}
51+
</div>
52+
<Button variant="primary" onPress={insertAnimal}>Insert Next Animal</Button>
53+
</>
54+
);
55+
}

0 commit comments

Comments
 (0)