use-resize-observer
Advanced tools
Comparing version 6.1.0 to 6.2.0-alpha.1
[ | ||
{ | ||
"path": "dist/bundle.esm.js", | ||
"limit": "357 B", | ||
"limit": "499 B", | ||
"gzip": true | ||
@@ -9,3 +9,3 @@ }, | ||
"path": "dist/bundle.esm.js", | ||
"limit": "281 B", | ||
"limit": "404 B", | ||
"brotli": true | ||
@@ -15,3 +15,3 @@ }, | ||
"path": "dist/bundle.cjs.js", | ||
"limit": "346 B", | ||
"limit": "485 B", | ||
"gzip": true | ||
@@ -21,3 +21,3 @@ }, | ||
"path": "dist/bundle.cjs.js", | ||
"limit": "261 B", | ||
"limit": "387 B", | ||
"brotli": true | ||
@@ -27,3 +27,3 @@ }, | ||
"path": "polyfilled.js", | ||
"limit": "2678 B", | ||
"limit": "2809 B", | ||
"gzip": true | ||
@@ -33,5 +33,5 @@ }, | ||
"path": "polyfilled.js", | ||
"limit": "2381 B", | ||
"limit": "2512 B", | ||
"brotli": true | ||
} | ||
] |
# CHANGELOG | ||
## 6.2.0-alpha.1 | ||
- Only instantiating a ResizeObserver instance if there's actually something to | ||
observe. This for example means that if you pass in `null` or undefined as the | ||
ref, or if neither the default ref or callback ref returned from the hook are | ||
in use, then no ResizeObserver instance will get created until there's an | ||
actual element to observe. Resolves: #42 | ||
- The hook now returns `callbackRef`, which can be used in place of the usual | ||
`ref`. Use this instead of a normal ref, when the observed component is | ||
mounted with a delay. Resolves: #43, #45 | ||
- The `ref` option now accepts raw elements as well. | ||
- Handling custom refs (through options), the default ref and the callback ref | ||
has been greatly refactored internally (into the `useResolvedElement` | ||
hook), to handle more edge cases with the way refs are handled. | ||
- Tests based on react testing library were refactored to make them much simpler | ||
and more approachable. | ||
- Fixed an error where in certain edge cases the hook tried to set state when | ||
its host component already unmounted. | ||
- Added [contributing guidelines](./CONTRIBUTING.md) | ||
- Overall bundle size increased a bit, due to the new features added. | ||
(With about ~150B or so.) | ||
## 6.1.0 | ||
@@ -7,7 +29,7 @@ | ||
## 6.1.0-alpha3 | ||
## 6.1.0-alpha.3 | ||
- Fixed SSR rendering, and added a test to cover it. | ||
## 6.1.0-alpha2 | ||
## 6.1.0-alpha.2 | ||
@@ -18,3 +40,3 @@ - ResizeObserver instances are no longer created unnecessarily when the onResize | ||
## 6.1.0-alpha1 | ||
## 6.1.0-alpha.1 | ||
@@ -21,0 +43,0 @@ - Rewrote the source in TypeScript. (Feedback is welcome.) |
@@ -5,2 +5,69 @@ 'use strict'; | ||
// This of course could've been more streamlined with internal state instead of | ||
// refs, but then host hooks / components could not opt out of renders. | ||
// This could've been exported to its own module, but the current build doesn't | ||
// seem to work with module imports and I had no more time to spend on this... | ||
function useResolvedElement(subscriber, refOrElement) { | ||
// The default ref has to be non-conditionally declared here whether or not | ||
// it'll be used as that's how hooks work. | ||
// @see https://reactjs.org/docs/hooks-rules.html#explanation | ||
var ref = react.useRef(null); // Default ref | ||
var refElement = react.useRef(null); | ||
var callbackRefElement = react.useRef(null); | ||
var callbackRef = react.useCallback(function (element) { | ||
callbackRefElement.current = element; | ||
callSubscriber(); | ||
}, []); | ||
var lastReportedElementRef = react.useRef(null); | ||
var cleanupRef = react.useRef(); | ||
var callSubscriber = function callSubscriber() { | ||
var element = null; | ||
if (callbackRefElement.current) { | ||
element = callbackRefElement.current; | ||
} else if (refElement.current) { | ||
element = refElement.current; | ||
} else if (refOrElement instanceof HTMLElement) { | ||
element = refOrElement; | ||
} | ||
if (lastReportedElementRef.current === element) { | ||
return; | ||
} | ||
if (cleanupRef.current) { | ||
cleanupRef.current(); | ||
} | ||
lastReportedElementRef.current = element; // Only calling the subscriber, if there's an actual element to report. | ||
if (element) { | ||
cleanupRef.current = subscriber(element); | ||
} | ||
}; | ||
if (refOrElement && !(refOrElement instanceof HTMLElement)) { | ||
// Overriding the default ref with the given one | ||
ref = refOrElement; | ||
} // On each render, we check whether a ref changed, or if we got a new raw | ||
// element. | ||
react.useEffect(function () { | ||
// Note that this does not mean that "element" will necessarily be whatever | ||
// the ref currently holds. It'll simply "update" `element` each render to | ||
// the current ref value, but there's no guarantee that the ref value will | ||
// not change later without a render. | ||
// This may or may not be a problem depending on the specific use case. | ||
refElement.current = ref.current; | ||
callSubscriber(); | ||
}, [ref, ref.current, refOrElement]); | ||
return { | ||
ref: ref, | ||
callbackRef: callbackRef | ||
}; | ||
} | ||
function useResizeObserver(opts) { | ||
@@ -11,15 +78,10 @@ if (opts === void 0) { | ||
// `defaultRef` Has to be non-conditionally declared here whether or not it'll | ||
// be used as that's how hooks work. | ||
// @see https://reactjs.org/docs/hooks-rules.html#explanation | ||
var defaultRef = react.useRef(null); // Saving the callback as a ref. With this, I don't need to put onResize in the | ||
// Saving the callback as a ref. With this, I don't need to put onResize in the | ||
// effect dep array, and just passing in an anonymous function without memoising | ||
// will not reinstantiate the hook's ResizeObserver | ||
var onResize = opts.onResize; | ||
var onResizeRef = react.useRef(undefined); | ||
onResizeRef.current = onResize; // Using a single instance throughought the hook's lifetime | ||
onResizeRef.current = onResize; // Using a single instance throughout the hook's lifetime | ||
var resizeObserverRef = react.useRef(); | ||
var ref = opts.ref || defaultRef; | ||
@@ -31,66 +93,80 @@ var _useState = react.useState({ | ||
size = _useState[0], | ||
setSize = _useState[1]; // Using a ref to track the previous width / height to avoid unnecessary renders | ||
setSize = _useState[1]; // In certain edge cases the RO might want to report a size change just after | ||
// the component unmounted. | ||
var didUnmount = react.useRef(false); | ||
react.useEffect(function () { | ||
return function () { | ||
didUnmount.current = true; | ||
}; | ||
}, []); // Using a ref to track the previous width / height to avoid unnecessary renders | ||
var previous = react.useRef({ | ||
width: undefined, | ||
height: undefined | ||
}); | ||
react.useEffect(function () { | ||
if (resizeObserverRef.current) { | ||
return; | ||
} | ||
}); // This block is kinda like a useEffect, only it's called whenever a new | ||
// element could be resolved based on the ref option. It also has a cleanup | ||
// function. | ||
resizeObserverRef.current = new ResizeObserver(function (entries) { | ||
if (!Array.isArray(entries)) { | ||
return; | ||
} // Since we only observe the one element, we don't need to loop over the | ||
// array | ||
var _useResolvedElement = useResolvedElement(function (element) { | ||
// Initialising the RO instance | ||
if (!resizeObserverRef.current) { | ||
// Saving a single instance, used by the hook from this point on. | ||
resizeObserverRef.current = new ResizeObserver(function (entries) { | ||
if (!Array.isArray(entries)) { | ||
return; | ||
} // Since we only observe the one element, we don't need to loop over the | ||
// array | ||
if (!entries.length) { | ||
return; | ||
} | ||
if (!entries.length) { | ||
return; | ||
} | ||
var entry = entries[0]; // `Math.round` is in line with how CSS resolves sub-pixel values | ||
var entry = entries[0]; // `Math.round` is in line with how CSS resolves sub-pixel values | ||
var newWidth = Math.round(entry.contentRect.width); | ||
var newHeight = Math.round(entry.contentRect.height); | ||
var newWidth = Math.round(entry.contentRect.width); | ||
var newHeight = Math.round(entry.contentRect.height); | ||
if (previous.current.width !== newWidth || previous.current.height !== newHeight) { | ||
var newSize = { | ||
width: newWidth, | ||
height: newHeight | ||
}; | ||
if (previous.current.width !== newWidth || previous.current.height !== newHeight) { | ||
var newSize = { | ||
width: newWidth, | ||
height: newHeight | ||
}; | ||
if (onResizeRef.current) { | ||
onResizeRef.current(newSize); | ||
} else { | ||
previous.current.width = newWidth; | ||
previous.current.height = newHeight; | ||
setSize(newSize); | ||
if (onResizeRef.current) { | ||
onResizeRef.current(newSize); | ||
} else { | ||
previous.current.width = newWidth; | ||
previous.current.height = newHeight; | ||
if (!didUnmount.current) { | ||
setSize(newSize); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
}, []); | ||
react.useEffect(function () { | ||
if (typeof ref !== "object" || ref === null || !(ref.current instanceof Element)) { | ||
return; | ||
}); | ||
} | ||
var element = ref.current; | ||
resizeObserverRef.current.observe(element); | ||
return function () { | ||
return resizeObserverRef.current.unobserve(element); | ||
if (resizeObserverRef.current) { | ||
resizeObserverRef.current.unobserve(element); | ||
} | ||
}; | ||
}, [ref]); | ||
}, opts.ref), | ||
ref = _useResolvedElement.ref, | ||
callbackRef = _useResolvedElement.callbackRef; | ||
return react.useMemo(function () { | ||
return { | ||
ref: ref, | ||
callbackRef: callbackRef, | ||
width: size.width, | ||
height: size.height | ||
}; | ||
}, [ref, size ? size.width : null, size ? size.height : null]); | ||
}, [ref, callbackRef, size ? size.width : null, size ? size.height : null]); | ||
} | ||
module.exports = useResizeObserver; |
@@ -1,3 +0,70 @@ | ||
import { useRef, useState, useEffect, useMemo } from 'react'; | ||
import { useRef, useState, useEffect, useMemo, useCallback } from 'react'; | ||
// This of course could've been more streamlined with internal state instead of | ||
// refs, but then host hooks / components could not opt out of renders. | ||
// This could've been exported to its own module, but the current build doesn't | ||
// seem to work with module imports and I had no more time to spend on this... | ||
function useResolvedElement(subscriber, refOrElement) { | ||
// The default ref has to be non-conditionally declared here whether or not | ||
// it'll be used as that's how hooks work. | ||
// @see https://reactjs.org/docs/hooks-rules.html#explanation | ||
var ref = useRef(null); // Default ref | ||
var refElement = useRef(null); | ||
var callbackRefElement = useRef(null); | ||
var callbackRef = useCallback(function (element) { | ||
callbackRefElement.current = element; | ||
callSubscriber(); | ||
}, []); | ||
var lastReportedElementRef = useRef(null); | ||
var cleanupRef = useRef(); | ||
var callSubscriber = function callSubscriber() { | ||
var element = null; | ||
if (callbackRefElement.current) { | ||
element = callbackRefElement.current; | ||
} else if (refElement.current) { | ||
element = refElement.current; | ||
} else if (refOrElement instanceof HTMLElement) { | ||
element = refOrElement; | ||
} | ||
if (lastReportedElementRef.current === element) { | ||
return; | ||
} | ||
if (cleanupRef.current) { | ||
cleanupRef.current(); | ||
} | ||
lastReportedElementRef.current = element; // Only calling the subscriber, if there's an actual element to report. | ||
if (element) { | ||
cleanupRef.current = subscriber(element); | ||
} | ||
}; | ||
if (refOrElement && !(refOrElement instanceof HTMLElement)) { | ||
// Overriding the default ref with the given one | ||
ref = refOrElement; | ||
} // On each render, we check whether a ref changed, or if we got a new raw | ||
// element. | ||
useEffect(function () { | ||
// Note that this does not mean that "element" will necessarily be whatever | ||
// the ref currently holds. It'll simply "update" `element` each render to | ||
// the current ref value, but there's no guarantee that the ref value will | ||
// not change later without a render. | ||
// This may or may not be a problem depending on the specific use case. | ||
refElement.current = ref.current; | ||
callSubscriber(); | ||
}, [ref, ref.current, refOrElement]); | ||
return { | ||
ref: ref, | ||
callbackRef: callbackRef | ||
}; | ||
} | ||
function useResizeObserver(opts) { | ||
@@ -8,15 +75,10 @@ if (opts === void 0) { | ||
// `defaultRef` Has to be non-conditionally declared here whether or not it'll | ||
// be used as that's how hooks work. | ||
// @see https://reactjs.org/docs/hooks-rules.html#explanation | ||
var defaultRef = useRef(null); // Saving the callback as a ref. With this, I don't need to put onResize in the | ||
// Saving the callback as a ref. With this, I don't need to put onResize in the | ||
// effect dep array, and just passing in an anonymous function without memoising | ||
// will not reinstantiate the hook's ResizeObserver | ||
var onResize = opts.onResize; | ||
var onResizeRef = useRef(undefined); | ||
onResizeRef.current = onResize; // Using a single instance throughought the hook's lifetime | ||
onResizeRef.current = onResize; // Using a single instance throughout the hook's lifetime | ||
var resizeObserverRef = useRef(); | ||
var ref = opts.ref || defaultRef; | ||
@@ -28,66 +90,80 @@ var _useState = useState({ | ||
size = _useState[0], | ||
setSize = _useState[1]; // Using a ref to track the previous width / height to avoid unnecessary renders | ||
setSize = _useState[1]; // In certain edge cases the RO might want to report a size change just after | ||
// the component unmounted. | ||
var didUnmount = useRef(false); | ||
useEffect(function () { | ||
return function () { | ||
didUnmount.current = true; | ||
}; | ||
}, []); // Using a ref to track the previous width / height to avoid unnecessary renders | ||
var previous = useRef({ | ||
width: undefined, | ||
height: undefined | ||
}); | ||
useEffect(function () { | ||
if (resizeObserverRef.current) { | ||
return; | ||
} | ||
}); // This block is kinda like a useEffect, only it's called whenever a new | ||
// element could be resolved based on the ref option. It also has a cleanup | ||
// function. | ||
resizeObserverRef.current = new ResizeObserver(function (entries) { | ||
if (!Array.isArray(entries)) { | ||
return; | ||
} // Since we only observe the one element, we don't need to loop over the | ||
// array | ||
var _useResolvedElement = useResolvedElement(function (element) { | ||
// Initialising the RO instance | ||
if (!resizeObserverRef.current) { | ||
// Saving a single instance, used by the hook from this point on. | ||
resizeObserverRef.current = new ResizeObserver(function (entries) { | ||
if (!Array.isArray(entries)) { | ||
return; | ||
} // Since we only observe the one element, we don't need to loop over the | ||
// array | ||
if (!entries.length) { | ||
return; | ||
} | ||
if (!entries.length) { | ||
return; | ||
} | ||
var entry = entries[0]; // `Math.round` is in line with how CSS resolves sub-pixel values | ||
var entry = entries[0]; // `Math.round` is in line with how CSS resolves sub-pixel values | ||
var newWidth = Math.round(entry.contentRect.width); | ||
var newHeight = Math.round(entry.contentRect.height); | ||
var newWidth = Math.round(entry.contentRect.width); | ||
var newHeight = Math.round(entry.contentRect.height); | ||
if (previous.current.width !== newWidth || previous.current.height !== newHeight) { | ||
var newSize = { | ||
width: newWidth, | ||
height: newHeight | ||
}; | ||
if (previous.current.width !== newWidth || previous.current.height !== newHeight) { | ||
var newSize = { | ||
width: newWidth, | ||
height: newHeight | ||
}; | ||
if (onResizeRef.current) { | ||
onResizeRef.current(newSize); | ||
} else { | ||
previous.current.width = newWidth; | ||
previous.current.height = newHeight; | ||
setSize(newSize); | ||
if (onResizeRef.current) { | ||
onResizeRef.current(newSize); | ||
} else { | ||
previous.current.width = newWidth; | ||
previous.current.height = newHeight; | ||
if (!didUnmount.current) { | ||
setSize(newSize); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
}, []); | ||
useEffect(function () { | ||
if (typeof ref !== "object" || ref === null || !(ref.current instanceof Element)) { | ||
return; | ||
}); | ||
} | ||
var element = ref.current; | ||
resizeObserverRef.current.observe(element); | ||
return function () { | ||
return resizeObserverRef.current.unobserve(element); | ||
if (resizeObserverRef.current) { | ||
resizeObserverRef.current.unobserve(element); | ||
} | ||
}; | ||
}, [ref]); | ||
}, opts.ref), | ||
ref = _useResolvedElement.ref, | ||
callbackRef = _useResolvedElement.callbackRef; | ||
return useMemo(function () { | ||
return { | ||
ref: ref, | ||
callbackRef: callbackRef, | ||
width: size.width, | ||
height: size.height | ||
}; | ||
}, [ref, size ? size.width : null, size ? size.height : null]); | ||
}, [ref, callbackRef, size ? size.width : null, size ? size.height : null]); | ||
} | ||
export default useResizeObserver; |
@@ -1,2 +0,2 @@ | ||
import { RefObject } from "react"; | ||
import { RefObject, RefCallback } from "react"; | ||
declare type ObservedSize = { | ||
@@ -7,13 +7,17 @@ width: number | undefined; | ||
declare type ResizeHandler = (size: ObservedSize) => void; | ||
declare function useResizeObserver<T extends HTMLElement>(opts?: { | ||
onResize?: ResizeHandler; | ||
}): { | ||
declare type HookResponse<T extends HTMLElement> = { | ||
ref: RefObject<T>; | ||
callbackRef: RefCallback<T>; | ||
} & ObservedSize; | ||
declare function useResizeObserver<T extends HTMLElement>(opts?: { | ||
onResize?: ResizeHandler; | ||
}): HookResponse<T>; | ||
declare function useResizeObserver<T extends HTMLElement>(opts?: { | ||
ref: RefObject<T>; | ||
onResize?: ResizeHandler; | ||
}): { | ||
ref: RefObject<T>; | ||
} & ObservedSize; | ||
}): HookResponse<T>; | ||
declare function useResizeObserver<T extends HTMLElement>(opts?: { | ||
ref: RefObject<T> | T | null | undefined; | ||
onResize?: ResizeHandler; | ||
}): HookResponse<T>; | ||
export default useResizeObserver; |
{ | ||
"name": "use-resize-observer", | ||
"version": "6.1.0", | ||
"version": "6.2.0-alpha.1", | ||
"main": "dist/bundle.cjs.js", | ||
@@ -48,5 +48,6 @@ "module": "dist/bundle.esm.js", | ||
"@babel/preset-typescript": "^7.9.0", | ||
"@rollup/plugin-babel": "^5.2.1", | ||
"@rollup/plugin-inject": "^4.0.1", | ||
"@size-limit/preset-small-lib": "^4.4.5", | ||
"@testing-library/react": "^10.0.2", | ||
"@testing-library/react": "^11.0.4", | ||
"@types/karma": "^5.0.0", | ||
@@ -62,3 +63,3 @@ "@types/karma-jasmine": "^3.1.0", | ||
"karma-firefox-launcher": "^1.3.0", | ||
"karma-jasmine": "^3.1.1", | ||
"karma-jasmine": "^4.0.1", | ||
"karma-sourcemap-loader": "^0.3.7", | ||
@@ -73,5 +74,4 @@ "karma-spec-reporter": "^0.0.32", | ||
"rollup": "^2.6.1", | ||
"rollup-plugin-babel": "^4.4.0", | ||
"size-limit": "^4.4.5", | ||
"typescript": "^3.8.3" | ||
"typescript": "^4.0.3" | ||
}, | ||
@@ -78,0 +78,0 @@ "dependencies": { |
@@ -1,2 +0,2 @@ | ||
import { RefObject } from "react"; | ||
import { RefObject, RefCallback } from "react"; | ||
declare type ObservedSize = { | ||
@@ -7,13 +7,17 @@ width: number | undefined; | ||
declare type ResizeHandler = (size: ObservedSize) => void; | ||
declare function useResizeObserver<T extends HTMLElement>(opts?: { | ||
onResize?: ResizeHandler; | ||
}): { | ||
declare type HookResponse<T extends HTMLElement> = { | ||
ref: RefObject<T>; | ||
callbackRef: RefCallback<T>; | ||
} & ObservedSize; | ||
declare function useResizeObserver<T extends HTMLElement>(opts?: { | ||
onResize?: ResizeHandler; | ||
}): HookResponse<T>; | ||
declare function useResizeObserver<T extends HTMLElement>(opts?: { | ||
ref: RefObject<T>; | ||
onResize?: ResizeHandler; | ||
}): { | ||
ref: RefObject<T>; | ||
} & ObservedSize; | ||
}): HookResponse<T>; | ||
declare function useResizeObserver<T extends HTMLElement>(opts?: { | ||
ref: RefObject<T> | T | null | undefined; | ||
onResize?: ResizeHandler; | ||
}): HookResponse<T>; | ||
export default useResizeObserver; |
'use strict'; | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var ResizeObserver = _interopDefault(require('resize-observer-polyfill')); | ||
var ResizeObserver = require('resize-observer-polyfill'); | ||
var react = require('react'); | ||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
var ResizeObserver__default = /*#__PURE__*/_interopDefaultLegacy(ResizeObserver); | ||
// This of course could've been more streamlined with internal state instead of | ||
// refs, but then host hooks / components could not opt out of renders. | ||
// This could've been exported to its own module, but the current build doesn't | ||
// seem to work with module imports and I had no more time to spend on this... | ||
function useResolvedElement(subscriber, refOrElement) { | ||
// The default ref has to be non-conditionally declared here whether or not | ||
// it'll be used as that's how hooks work. | ||
// @see https://reactjs.org/docs/hooks-rules.html#explanation | ||
var ref = react.useRef(null); // Default ref | ||
var refElement = react.useRef(null); | ||
var callbackRefElement = react.useRef(null); | ||
var callbackRef = react.useCallback(function (element) { | ||
callbackRefElement.current = element; | ||
callSubscriber(); | ||
}, []); | ||
var lastReportedElementRef = react.useRef(null); | ||
var cleanupRef = react.useRef(); | ||
var callSubscriber = function callSubscriber() { | ||
var element = null; | ||
if (callbackRefElement.current) { | ||
element = callbackRefElement.current; | ||
} else if (refElement.current) { | ||
element = refElement.current; | ||
} else if (refOrElement instanceof HTMLElement) { | ||
element = refOrElement; | ||
} | ||
if (lastReportedElementRef.current === element) { | ||
return; | ||
} | ||
if (cleanupRef.current) { | ||
cleanupRef.current(); | ||
} | ||
lastReportedElementRef.current = element; // Only calling the subscriber, if there's an actual element to report. | ||
if (element) { | ||
cleanupRef.current = subscriber(element); | ||
} | ||
}; | ||
if (refOrElement && !(refOrElement instanceof HTMLElement)) { | ||
// Overriding the default ref with the given one | ||
ref = refOrElement; | ||
} // On each render, we check whether a ref changed, or if we got a new raw | ||
// element. | ||
react.useEffect(function () { | ||
// Note that this does not mean that "element" will necessarily be whatever | ||
// the ref currently holds. It'll simply "update" `element` each render to | ||
// the current ref value, but there's no guarantee that the ref value will | ||
// not change later without a render. | ||
// This may or may not be a problem depending on the specific use case. | ||
refElement.current = ref.current; | ||
callSubscriber(); | ||
}, [ref, ref.current, refOrElement]); | ||
return { | ||
ref: ref, | ||
callbackRef: callbackRef | ||
}; | ||
} | ||
function useResizeObserver(opts) { | ||
@@ -13,15 +82,10 @@ if (opts === void 0) { | ||
// `defaultRef` Has to be non-conditionally declared here whether or not it'll | ||
// be used as that's how hooks work. | ||
// @see https://reactjs.org/docs/hooks-rules.html#explanation | ||
var defaultRef = react.useRef(null); // Saving the callback as a ref. With this, I don't need to put onResize in the | ||
// Saving the callback as a ref. With this, I don't need to put onResize in the | ||
// effect dep array, and just passing in an anonymous function without memoising | ||
// will not reinstantiate the hook's ResizeObserver | ||
var onResize = opts.onResize; | ||
var onResizeRef = react.useRef(undefined); | ||
onResizeRef.current = onResize; // Using a single instance throughought the hook's lifetime | ||
onResizeRef.current = onResize; // Using a single instance throughout the hook's lifetime | ||
var resizeObserverRef = react.useRef(); | ||
var ref = opts.ref || defaultRef; | ||
@@ -33,66 +97,80 @@ var _useState = react.useState({ | ||
size = _useState[0], | ||
setSize = _useState[1]; // Using a ref to track the previous width / height to avoid unnecessary renders | ||
setSize = _useState[1]; // In certain edge cases the RO might want to report a size change just after | ||
// the component unmounted. | ||
var didUnmount = react.useRef(false); | ||
react.useEffect(function () { | ||
return function () { | ||
didUnmount.current = true; | ||
}; | ||
}, []); // Using a ref to track the previous width / height to avoid unnecessary renders | ||
var previous = react.useRef({ | ||
width: undefined, | ||
height: undefined | ||
}); | ||
react.useEffect(function () { | ||
if (resizeObserverRef.current) { | ||
return; | ||
} | ||
}); // This block is kinda like a useEffect, only it's called whenever a new | ||
// element could be resolved based on the ref option. It also has a cleanup | ||
// function. | ||
resizeObserverRef.current = new ResizeObserver(function (entries) { | ||
if (!Array.isArray(entries)) { | ||
return; | ||
} // Since we only observe the one element, we don't need to loop over the | ||
// array | ||
var _useResolvedElement = useResolvedElement(function (element) { | ||
// Initialising the RO instance | ||
if (!resizeObserverRef.current) { | ||
// Saving a single instance, used by the hook from this point on. | ||
resizeObserverRef.current = new ResizeObserver__default['default'](function (entries) { | ||
if (!Array.isArray(entries)) { | ||
return; | ||
} // Since we only observe the one element, we don't need to loop over the | ||
// array | ||
if (!entries.length) { | ||
return; | ||
} | ||
if (!entries.length) { | ||
return; | ||
} | ||
var entry = entries[0]; // `Math.round` is in line with how CSS resolves sub-pixel values | ||
var entry = entries[0]; // `Math.round` is in line with how CSS resolves sub-pixel values | ||
var newWidth = Math.round(entry.contentRect.width); | ||
var newHeight = Math.round(entry.contentRect.height); | ||
var newWidth = Math.round(entry.contentRect.width); | ||
var newHeight = Math.round(entry.contentRect.height); | ||
if (previous.current.width !== newWidth || previous.current.height !== newHeight) { | ||
var newSize = { | ||
width: newWidth, | ||
height: newHeight | ||
}; | ||
if (previous.current.width !== newWidth || previous.current.height !== newHeight) { | ||
var newSize = { | ||
width: newWidth, | ||
height: newHeight | ||
}; | ||
if (onResizeRef.current) { | ||
onResizeRef.current(newSize); | ||
} else { | ||
previous.current.width = newWidth; | ||
previous.current.height = newHeight; | ||
setSize(newSize); | ||
if (onResizeRef.current) { | ||
onResizeRef.current(newSize); | ||
} else { | ||
previous.current.width = newWidth; | ||
previous.current.height = newHeight; | ||
if (!didUnmount.current) { | ||
setSize(newSize); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
}, []); | ||
react.useEffect(function () { | ||
if (typeof ref !== "object" || ref === null || !(ref.current instanceof Element)) { | ||
return; | ||
}); | ||
} | ||
var element = ref.current; | ||
resizeObserverRef.current.observe(element); | ||
return function () { | ||
return resizeObserverRef.current.unobserve(element); | ||
if (resizeObserverRef.current) { | ||
resizeObserverRef.current.unobserve(element); | ||
} | ||
}; | ||
}, [ref]); | ||
}, opts.ref), | ||
ref = _useResolvedElement.ref, | ||
callbackRef = _useResolvedElement.callbackRef; | ||
return react.useMemo(function () { | ||
return { | ||
ref: ref, | ||
callbackRef: callbackRef, | ||
width: size.width, | ||
height: size.height | ||
}; | ||
}, [ref, size ? size.width : null, size ? size.height : null]); | ||
}, [ref, callbackRef, size ? size.width : null, size ? size.height : null]); | ||
} | ||
module.exports = useResizeObserver; |
105
README.md
@@ -18,8 +18,10 @@ # use-resize-observer | ||
- Written in **TypeScript**. | ||
- **Tiny**: 353 B (minified, gzipped) Monitored by [size-limit](https://github.com/ai/size-limit). | ||
- **Tiny**: 500B (minified, gzipped) Monitored by [size-limit](https://github.com/ai/size-limit). | ||
- Exposes an **onResize callback** if you need more control. | ||
- [Throttle / Debounce](#throttle--debounce) | ||
- Works with **SSR**. | ||
- Works with **CSS-in-JS**. | ||
- **Supports custom refs** in case you [had one already](#passing-in-your-own-ref). | ||
- Handles many edge cases you might not even think of. | ||
(See this documentation and the test cases.) | ||
- [Throttle / Debounce](#throttle--debounce) | ||
- **Tested** in real browsers. (Headless Chrome and Firefox). | ||
@@ -44,3 +46,3 @@ | ||
```js | ||
```tsx | ||
import React from "react"; | ||
@@ -50,3 +52,3 @@ import useResizeObserver from "use-resize-observer"; | ||
const App = () => { | ||
const { ref, width = 1, height = 1 } = useResizeObserver(); | ||
const { ref, width = 1, height = 1 } = useResizeObserver<HTMLDivElement>(); | ||
@@ -66,5 +68,5 @@ return ( | ||
```js | ||
const ref = useRef(null); | ||
const { width, height } = useResizeObserver({ ref }); | ||
```ts | ||
const ref = useRef<HTMLDivElement>(null); | ||
const { width, height } = useResizeObserver<HTMLDivElement>({ ref }); | ||
``` | ||
@@ -76,2 +78,79 @@ | ||
## Measuring a raw element | ||
There might be situations where you have an element already that you need to measure. | ||
`ref` now accepts elements as well, not just refs, which means that you can do this: | ||
```ts | ||
const { width, height } = useResizeObserver<HTMLDivElement>({ | ||
ref: divElement, | ||
}); | ||
``` | ||
## Observing components mounted with a delay | ||
Often times you might have to wait before you can mount a component. | ||
Unfortunately if you use a ref, the hook will not be able to pick up its "current" | ||
value being changed afterwards. (Which is by design by React.) | ||
To handle this case, you can do one of these three: | ||
- Use the provided callback ref | ||
- Refactor to a component which you can mount after a "loading" concluded with | ||
the hook within it. This means that the hook will be mounted with the measured | ||
element, which means that a regular ref will work fine. | ||
- Use local state to store the custom ref the hook needs to observe, with "null" | ||
as its starting value. Then once loading concluded, set this state to the ref. | ||
This'll make the hook notice a change from null to the ref, where this time the | ||
ref actually has its current value set properly as well. | ||
([See example here](https://codesandbox.io/s/damp-cookies-6bdrg?file=/src/App.js)) | ||
Using a callback ref is recommended, and might look like this: | ||
```tsx | ||
const { callbackRef, width, height } = useResizeObserver<HTMLDivElement>(); | ||
const [loaded, setLoaded] = useState(false); | ||
// Simulating a loading state. | ||
useEffect(() => { | ||
setTimeout(() => { | ||
setLoaded(true); | ||
}, 2000); | ||
}, []); | ||
if (!loaded) { | ||
return <div>Loading...</div>; | ||
} | ||
return <div ref={callbackRef}></div>; | ||
``` | ||
## Using a single hook to measure multiple refs | ||
The hook reacts to ref changes, as it resolves it to an element to observe. | ||
This means that you can freely change the custom `ref` option from one ref to | ||
another and back, and the hook will start observing whatever is set in its options. | ||
## Opting Out of (or Delaying) ResizeObserver instantiation | ||
In certain cases you might want to delay creating a ResizeObserver instance. | ||
You might provide a library, that only optionally provides observation features | ||
based on props, which means that while you have the hook within your component, | ||
you might not want to actually initialise it. | ||
Another example is that you might want to entirely opt out of initialising, when | ||
you run some tests, where the environment does not provide the `ResizeObserver`. | ||
([See discussions](https://github.com/ZeeCoder/use-resize-observer/issues/40)) | ||
You can do one of the following depending on your needs: | ||
- Use a callback ref, or provide a custom ref conditionally, only when needed. | ||
The hook will not create a ResizeObserver instance up until there's something | ||
there to actually observe. | ||
- Patch the test environment, and make a polyfill available as the ResizeObserver. | ||
(This assumes you don't already use the polyfilled version, which would switch | ||
to the polyfill when no native implementation was available.) | ||
## The "onResize" callback | ||
@@ -86,3 +165,3 @@ | ||
```js | ||
```tsx | ||
import React from "react"; | ||
@@ -93,3 +172,3 @@ import useResizeObserver from "use-resize-observer"; | ||
// width / height will not be returned here when the onResize callback is present | ||
const { ref } = useResizeObserver({ | ||
const { ref } = useResizeObserver<HTMLDivElement>({ | ||
onResize: ({ width, height }) => { | ||
@@ -129,4 +208,4 @@ // do something here. | ||
```js | ||
const { ref, width = 100, height = 50 } = useResizeObserver(); | ||
```ts | ||
const { ref, width = 100, height = 50 } = useResizeObserver<HTMLDivElement>(); | ||
``` | ||
@@ -142,4 +221,4 @@ | ||
```js | ||
const { ref, width, height } = useResizeObserver(); | ||
```ts | ||
const { ref, width, height } = useResizeObserver<HTMLDivElement>(); | ||
``` | ||
@@ -146,0 +225,0 @@ |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
36155
12
495
250
2