From 7ec0ea8c9d7565048fb9c01e0a1c3ef6ab84a627 Mon Sep 17 00:00:00 2001 From: Omar Diab Date: Wed, 4 Mar 2020 18:49:50 +0900 Subject: [PATCH 1/7] add resize-observer-polyfill as a dev dep to get access to bundled types --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index cd856df..082c013 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "raf": "3.4.0", "react": "16.8.1", "react-dom": "16.8.1", - "react-test-renderer": "16.8.1" + "react-test-renderer": "16.8.1", + "resize-observer-polyfill": "^1.5.1" }, "ava": { "require": [ From 8782515f94e275b24747d9aad4454817a2d34340 Mon Sep 17 00:00:00 2001 From: Omar Diab Date: Wed, 4 Mar 2020 18:50:02 +0900 Subject: [PATCH 2/7] update code to take ResizeObserver option --- index.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 5e652a2..2474e00 100644 --- a/index.js +++ b/index.js @@ -18,7 +18,12 @@ function getSize(el) { } } -function useComponentSize(ref) { +function useComponentSize(ref, opts) { + var ResizeObserverConstructor = opts && opts.ResizeObserver + ? opts.ResizeObserver + : typeof ResizeObserver === 'function' + ? ResizeObserver + : undefined; var _useState = useState(getSize(ref ? ref.current : {})) var ComponentSize = _useState[0] var setComponentSize = _useState[1] @@ -40,8 +45,8 @@ function useComponentSize(ref) { handleResize() - if (typeof ResizeObserver === 'function') { - var resizeObserver = new ResizeObserver(function() { + if (ResizeObserverConstructor) { + var resizeObserver = new ResizeObserverConstructor(function() { handleResize() }) resizeObserver.observe(ref.current) From b6c2a3fe85be6324464b059d290b7ca2ad4293c3 Mon Sep 17 00:00:00 2001 From: Omar Diab Date: Wed, 4 Mar 2020 18:50:11 +0900 Subject: [PATCH 3/7] typescript typings --- index.d.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 8d655d4..107026c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -3,4 +3,8 @@ interface ComponentSize { height: number } -export default function useComponentSize(ref: React.RefObject): ComponentSize +interface ComponentSizeOptions { + ResizeObserver?: ResizeObserver; +} + +export default function useComponentSize(ref: React.RefObject, opts?: ComponentSizeOptions): ComponentSize From e490e443391ec6d3d6273bed11afad2f0c9f24a4 Mon Sep 17 00:00:00 2001 From: Omar Diab Date: Wed, 4 Mar 2020 18:50:20 +0900 Subject: [PATCH 4/7] flow typings (though idk if this is correct) --- index.js.flow | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/index.js.flow b/index.js.flow index 18dc8a8..e350dfe 100644 --- a/index.js.flow +++ b/index.js.flow @@ -3,4 +3,8 @@ interface ComponentSize { height: number, } -declare export default function useComponentSize(ref: React.Ref): ComponentSize; \ No newline at end of file +interface ComponentSizeOptions { + ResizeObserver?: ResizeObserver; +} + +declare export default function useComponentSize(ref: React.Ref, opts?: ComponentSizeOptions): ComponentSize; \ No newline at end of file From 25f40f0d464f27c00ba72d8d7777bb48b9f9f5f6 Mon Sep 17 00:00:00 2001 From: Omar Diab Date: Wed, 4 Mar 2020 20:36:33 +0900 Subject: [PATCH 5/7] add resize observer constructor as a dependency in useLayoutEffect --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 2474e00..9f5f7ca 100644 --- a/index.js +++ b/index.js @@ -63,7 +63,7 @@ function useComponentSize(ref, opts) { } } }, - [ref.current] + [ref.current, ResizeObserverConstructor] ) return ComponentSize From 192df2812fa1c8cae97d3e127a6a7f21b1473c2a Mon Sep 17 00:00:00 2001 From: Omar Diab Date: Wed, 4 Mar 2020 20:40:20 +0900 Subject: [PATCH 6/7] update docs to use resize-observer-polyfill as a ponyfill --- README.md | 69 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 8bf59aa..39766e8 100644 --- a/README.md +++ b/README.md @@ -29,52 +29,69 @@ function MyComponent() { ## ResizeObserver -[Resize Observer](https://developers.google.com/web/updates/2016/10/resizeobserver) -is the API is used to determine if an element is resized. Browser support is pretty good in Chrome, but is still missing support in other major browsers. +If it is present, this library uses the recent [`ResizeObserver` browser +API](https://developers.google.com/web/updates/2016/10/resizeobserver) to +determine if an element's content size has changed. + +If a browser does not have the `ResizeObserver` API present, then this library +falls back to listening on window size changes, which is less efficient and does +not listen for changes to the component's size due to other factors like content +changes. If it is not present, you can use pass a `ResizeObserver` +implementation into the `useComponentSize()` hook (see below). + +Browser support is pretty good in Chrome, but is still missing support in other +major browsers. > [Can i use ResizeObserver?](https://caniuse.com/#feat=resizeobserver) ### Polyfill -You can import the -[polyfill](https://github.com/que-etc/resize-observer-polyfill) directly from here +You can import [a ResizeObserver +ponyfill](https://github.com/que-etc/resize-observer-polyfill) with this NPM +library: ```sh yarn add resize-observer-polyfill ``` -Then import it in your app: +Then use it with the `useComponentSize()` hook: ```js -import 'resize-observer-polyfill' +import ResizeObserver from 'resize-observer-polyfill' +// ... +useComponentSize(ref, { ResizeObserver }); ``` If you are using Webpack (or similar) you could use [dynamic -imports](https://webpack.js.org/api/module-methods/#import-), to load the +imports](https://webpack.js.org/api/module-methods/#import), to load the Polyfill only if needed. A basic implementation could look something like this: ```js -loadPolyfills() - .then(() => /* Render React application now that your Polyfills are ready */) - -/** -* Do feature detection, to figure out which polyfills needs to be imported. -**/ -function loadPolyfills() { - const polyfills = [] - - if (!supportsResizeObserver()) { - polyfills.push(import('resize-observer-polyfill')) - } - - return Promise.all(polyfills) -} - -function supportsResizeObserver() { - return ( +function getResizeObserver() { + if ( 'ResizeObserver' in global && 'ResizeObserverEntry' in global && 'contentRect' in ResizeObserverEntry.prototype - ) + ) { + return Promise.resolve(ResizeObserver); + } + return import('resize-observer-polyfill'); } ``` + +And in your component: +```js +const [ResizeObserverApi, setResizeObserverApi] = setState(); +useEffect(() => { + let cancelled = false; + getResizeObserver().then(observer => { + if (!cancelled) { + setResizeObserverApi(observer); + } + }); + return () => { + cancelled = true; + } +}, []); +useComponentSize(ref, { ResizeObserver: ResizeObserverApi }); +``` From ab49a0a96edb9aea229e71eb3e9b94262c4cc508 Mon Sep 17 00:00:00 2001 From: Omar Diab Date: Thu, 5 Mar 2020 12:13:19 +0900 Subject: [PATCH 7/7] update readme example for lazy loading --- README.md | 45 +++++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 39766e8..bc812fd 100644 --- a/README.md +++ b/README.md @@ -64,34 +64,51 @@ useComponentSize(ref, { ResizeObserver }); If you are using Webpack (or similar) you could use [dynamic imports](https://webpack.js.org/api/module-methods/#import), to load the -Polyfill only if needed. A basic implementation could look something like this: +Polyfill only if needed. A basic implementation in your component could look +something like this: ```js -function getResizeObserver() { - if ( - 'ResizeObserver' in global && - 'ResizeObserverEntry' in global && - 'contentRect' in ResizeObserverEntry.prototype - ) { - return Promise.resolve(ResizeObserver); +// GetResizeObserver.js +// Ponyfill, not polyfill, since we're not chaging the global +// `window.ResizeObserver` variable +let ResizeObserverPonyfill; + +export async function getResizeObserverOrPonyfill() { + const BuiltinResizeObserver = window.ResizeObserver; + if (BuiltinResizeObserver) { + return BuiltinResizeObserver; } - return import('resize-observer-polyfill'); + ResizeObserverPonyfill = (await import("resize-observer-polyfill")).default; + return ResizeObserverPonyfill; } + ``` -And in your component: ```js -const [ResizeObserverApi, setResizeObserverApi] = setState(); +// Your component +const [ResizeObserver, setResizeObserver] = useState( + window.ResizeObserver); + useEffect(() => { + if (ResizeObserver) { + return; // don't need to load the ponyfill + } let cancelled = false; - getResizeObserver().then(observer => { + + // if imported multiple times, should load from browser cache; + // or you can cache this in a variable + getResizeObserverOrPonyfill().then(lib => { if (!cancelled) { - setResizeObserverApi(observer); + // must wrap `lib` in a function: `ResizeObserver` is a function + // itself, so prevent the state hook from interpreting as a reducer + setResizeObserver(() => lib); } }); + return () => { + // if unmounted before complete, don't call set state cancelled = true; } }, []); -useComponentSize(ref, { ResizeObserver: ResizeObserverApi }); +useComponentSize(ref, { ResizeObserver }); ```