From 0681218287d8d00bf9216762ef12549c5e1c6139 Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Fri, 25 Feb 2022 17:34:32 -0700 Subject: [PATCH 1/8] Fix useResizeObserver loop limit exceeded warning --- packages/@react-aria/utils/src/useResizeObserver.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/@react-aria/utils/src/useResizeObserver.ts b/packages/@react-aria/utils/src/useResizeObserver.ts index aa5f86fea7f..00314c05dfb 100644 --- a/packages/@react-aria/utils/src/useResizeObserver.ts +++ b/packages/@react-aria/utils/src/useResizeObserver.ts @@ -24,14 +24,20 @@ export function useResizeObserver(options: useResizeObser window.removeEventListener('resize', onResize, false); }; } else { - const resizeObserverInstance = new window.ResizeObserver((entries) => { if (!entries.length) { return; } onResize(); + + resizeObserverInstance.unobserve(element); + + requestAnimationFrame(() => { + resizeObserverInstance.observe(element); + }); }); + resizeObserverInstance.observe(element); return () => { From a3dba5fe53000bffef87a02617a4e24cf7bd95fe Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Fri, 4 Mar 2022 17:37:11 -0800 Subject: [PATCH 2/8] Make a build with the reproduction --- .../utils/src/useResizeObserver.ts | 6 -- .../stories/useResizeObserver.stories.tsx | 55 +++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 packages/@react-aria/utils/stories/useResizeObserver.stories.tsx diff --git a/packages/@react-aria/utils/src/useResizeObserver.ts b/packages/@react-aria/utils/src/useResizeObserver.ts index 00314c05dfb..805cb7b923b 100644 --- a/packages/@react-aria/utils/src/useResizeObserver.ts +++ b/packages/@react-aria/utils/src/useResizeObserver.ts @@ -30,12 +30,6 @@ export function useResizeObserver(options: useResizeObser } onResize(); - - resizeObserverInstance.unobserve(element); - - requestAnimationFrame(() => { - resizeObserverInstance.observe(element); - }); }); resizeObserverInstance.observe(element); diff --git a/packages/@react-aria/utils/stories/useResizeObserver.stories.tsx b/packages/@react-aria/utils/stories/useResizeObserver.stories.tsx new file mode 100644 index 00000000000..5e620dc83ff --- /dev/null +++ b/packages/@react-aria/utils/stories/useResizeObserver.stories.tsx @@ -0,0 +1,55 @@ + +import {Button} from '@react-spectrum/button'; +import {Meta, Story} from '@storybook/react'; +import React, {useEffect, useRef, useState} from 'react'; +import {useResizeObserver} from '../src'; + +export default { + title: 'useResizeObserver' +} as Meta; + +const Template: Story = () => ( + +); + +export const UseResizeObserverLoopLimit = Template.bind({}); +UseResizeObserverLoopLimit.args = {}; + +const animalSet = ['🐰', '🦊', '🐻', '🐭', '🐼', '🐸']; +function App() { + let ref = useRef(); + let index = useRef(0); + let [animals, setAnimals] = useState([animalSet[0]]); + + function insertAnimal() { + index.current = index.current + 1; + setAnimals(prev => [...prev, animalSet[index.current % animalSet.length]]); + } + + useEffect(() => { + let onError = (err) => { + console.log(err); + }; + window.addEventListener('error', onError); + return () => { + window.removeEventListener('error', onError); + }; + }, []); + + let onResize = () => { + const {width} = ref.current.getBoundingClientRect(); + + ref.current.style.height = `${width}px`; + }; + + useResizeObserver({onResize, ref}); + + return ( + <> +
+ {animals} +
+ + + ); +} From 5d7a392105295d5d98d3eba62a9741e0618d85b8 Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Fri, 4 Mar 2022 17:40:18 -0800 Subject: [PATCH 3/8] Add logic to see if we should stop observing --- .../@react-aria/utils/src/useResizeObserver.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/@react-aria/utils/src/useResizeObserver.ts b/packages/@react-aria/utils/src/useResizeObserver.ts index 805cb7b923b..03770be30ec 100644 --- a/packages/@react-aria/utils/src/useResizeObserver.ts +++ b/packages/@react-aria/utils/src/useResizeObserver.ts @@ -28,8 +28,22 @@ export function useResizeObserver(options: useResizeObser if (!entries.length) { return; } + let initialSize = element.getBoundingClientRect(); onResize(); + + let newSize = element.getBoundingClientRect(); + + if ( + initialSize.width !== newSize.width || + initialSize.height !== newSize.height + ) { + resizeObserverInstance.unobserve(element); + + requestAnimationFrame(() => { + resizeObserverInstance.observe(element); + }); + } }); resizeObserverInstance.observe(element); From c3161790919a396a6336c94a83174338e9f974a1 Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Fri, 11 Mar 2022 10:28:06 -0800 Subject: [PATCH 4/8] fix types --- .../@react-aria/utils/stories/useResizeObserver.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@react-aria/utils/stories/useResizeObserver.stories.tsx b/packages/@react-aria/utils/stories/useResizeObserver.stories.tsx index 5e620dc83ff..7a4a335d77c 100644 --- a/packages/@react-aria/utils/stories/useResizeObserver.stories.tsx +++ b/packages/@react-aria/utils/stories/useResizeObserver.stories.tsx @@ -17,7 +17,7 @@ UseResizeObserverLoopLimit.args = {}; const animalSet = ['🐰', '🦊', '🐻', '🐭', '🐼', '🐸']; function App() { - let ref = useRef(); + let ref = useRef(); let index = useRef(0); let [animals, setAnimals] = useState([animalSet[0]]); From 258cff6a88c0d87f7f5f138e9fe41df0dce0c4aa Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Tue, 7 Feb 2023 10:55:13 -0800 Subject: [PATCH 5/8] use raf instead of unobserve --- .../utils/src/useResizeObserver.ts | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/packages/@react-aria/utils/src/useResizeObserver.ts b/packages/@react-aria/utils/src/useResizeObserver.ts index fd3647db8c4..5e7810f3cc2 100644 --- a/packages/@react-aria/utils/src/useResizeObserver.ts +++ b/packages/@react-aria/utils/src/useResizeObserver.ts @@ -1,4 +1,4 @@ -import {RefObject, useEffect} from 'react'; +import {RefObject, useEffect, useRef} from 'react'; function hasResizeObserver() { return typeof window.ResizeObserver !== 'undefined'; @@ -11,6 +11,7 @@ type useResizeObserverOptionsType = { export function useResizeObserver(options: useResizeObserverOptionsType) { const {ref, onResize} = options; + let raf = useRef(null); useEffect(() => { let element = ref?.current; @@ -25,30 +26,23 @@ export function useResizeObserver(options: useResizeObserverO }; } else { const resizeObserverInstance = new window.ResizeObserver((entries) => { - if (!entries.length) { - return; - } - let initialSize = element.getBoundingClientRect(); - - onResize(); - - let newSize = element.getBoundingClientRect(); - - if ( - initialSize.width !== newSize.width || - initialSize.height !== newSize.height - ) { - resizeObserverInstance.unobserve(element); - - requestAnimationFrame(() => { - resizeObserverInstance.observe(element); - }); + if (raf.current) { + window.cancelAnimationFrame(raf.current); } + raf.current = window.requestAnimationFrame(() => { + if (!Array.isArray(entries) || !entries.length) { + return; + } + onResize(); + }); }); resizeObserverInstance.observe(element); return () => { + if (raf.current) { + window.cancelAnimationFrame(raf.current); + } if (element) { resizeObserverInstance.unobserve(element); } From 395334c8886833288b82e45e4f5dc8826edb6416 Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Tue, 7 Feb 2023 10:57:50 -0800 Subject: [PATCH 6/8] add comment --- packages/@react-aria/utils/src/useResizeObserver.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@react-aria/utils/src/useResizeObserver.ts b/packages/@react-aria/utils/src/useResizeObserver.ts index 5e7810f3cc2..64642254fa7 100644 --- a/packages/@react-aria/utils/src/useResizeObserver.ts +++ b/packages/@react-aria/utils/src/useResizeObserver.ts @@ -29,6 +29,8 @@ export function useResizeObserver(options: useResizeObserverO if (raf.current) { window.cancelAnimationFrame(raf.current); } + // avoid Error - ResizeObserver loop limit exceeded + // it's ok to use a raf, ResizeObservers are already async and now we're just debouncing on frames raf.current = window.requestAnimationFrame(() => { if (!Array.isArray(entries) || !entries.length) { return; From ab1b9318bceba4e50f799655377297370d26d196 Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Tue, 7 Mar 2023 22:39:07 -0800 Subject: [PATCH 7/8] clean up refs --- packages/@react-aria/utils/src/useResizeObserver.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/@react-aria/utils/src/useResizeObserver.ts b/packages/@react-aria/utils/src/useResizeObserver.ts index 64642254fa7..08c15b42336 100644 --- a/packages/@react-aria/utils/src/useResizeObserver.ts +++ b/packages/@react-aria/utils/src/useResizeObserver.ts @@ -28,10 +28,12 @@ export function useResizeObserver(options: useResizeObserverO const resizeObserverInstance = new window.ResizeObserver((entries) => { if (raf.current) { window.cancelAnimationFrame(raf.current); + raf.current = null; } // avoid Error - ResizeObserver loop limit exceeded // it's ok to use a raf, ResizeObservers are already async and now we're just debouncing on frames raf.current = window.requestAnimationFrame(() => { + raf.current = null; if (!Array.isArray(entries) || !entries.length) { return; } @@ -44,6 +46,7 @@ export function useResizeObserver(options: useResizeObserverO return () => { if (raf.current) { window.cancelAnimationFrame(raf.current); + raf.current = null; } if (element) { resizeObserverInstance.unobserve(element); From 0fa77cfa6945429628b624c0b823c5b18b4e2fa6 Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Fri, 10 Mar 2023 13:36:03 -0800 Subject: [PATCH 8/8] fix potential scenario where we never fire onResize --- packages/@react-aria/utils/src/useResizeObserver.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/@react-aria/utils/src/useResizeObserver.ts b/packages/@react-aria/utils/src/useResizeObserver.ts index 08c15b42336..e2cb9530ec2 100644 --- a/packages/@react-aria/utils/src/useResizeObserver.ts +++ b/packages/@react-aria/utils/src/useResizeObserver.ts @@ -27,8 +27,7 @@ export function useResizeObserver(options: useResizeObserverO } else { const resizeObserverInstance = new window.ResizeObserver((entries) => { if (raf.current) { - window.cancelAnimationFrame(raf.current); - raf.current = null; + return; } // avoid Error - ResizeObserver loop limit exceeded // it's ok to use a raf, ResizeObservers are already async and now we're just debouncing on frames