Add hook for measuring components
kripod opened this issue · 5 comments
kripod commented
Motivation
One may need to know the exact size of a DOM node, to know whether it's big or small enough.
Details
How can I measure a DOM node? – React Hooks FAQ:
function MeasureExample() {
const [rect, ref] = useClientRect();
return (
<>
<h1 ref={ref}>Hello, world</h1>
{rect !== null &&
<h2>The above header is {Math.round(rect.height)}px tall</h2>
}
</>
);
}
function useClientRect() {
const [rect, setRect] = useState(null);
const ref = useCallback(node => {
if (node !== null) {
setRect(node.getBoundingClientRect());
}
}, []);
return [rect, ref];
}
cmoog commented
Seems like you'd need to be able to refresh the measurement, maybe on a scroll event or after some animation etc. What do you think about the following modification?
Simple usage
const [rect, ref, refresh] = useNodeRect()
Implementation
function useNodeRect() {
const [rect, setRect] = useState(null)
const ref = useRef()
const refresh = useCallback(ref => {
if (ref.current) {
setRect(ref.current.getBoundingClientRect())
}
}, [])
useEffect(() => {
refresh(ref)
}, [ref])
return [rect, ref, refresh]
}
kripod commented
Personally, I'm thinking about using the ResizeObserver
API, but it comes with the cost of polyfilling as of today:
import { useCallback, useMemo, useState, useEffect } from 'react';
import ResizeObserver from 'resize-observer-polyfill';
export default function useSize() {
const [size, setSize] = useState<Readonly<[number, number]>>([0, 0]);
const ro = useMemo(
() =>
new ResizeObserver(([entry]) => {
setSize([entry.contentRect.width, entry.contentRect.height]);
}),
[],
);
const ref = useCallback(
node => {
if (node) {
ro.observe(node);
}
},
[ro],
);
useEffect(() => {
return () => {
ro.disconnect();
};
}, [ro]);
return [size, ref];
}
Also, I'm not entirely sure about the ro.disconnect()
part.
cmoog commented
Looks like that's what react-use
has as well.
kripod commented
Here is an improved snippet of code, which makes sure not to observe the same target multiple times:
import { useCallback, useMemo, useState } from 'react';
import ResizeObserver from 'resize-observer-polyfill';
export default function useSize(): [
Readonly<[number, number]>,
(element: HTMLElement) => void,
] {
const [size, setSize] = useState<Readonly<[number, number]>>([0, 0]);
const ro = useMemo(
() =>
new ResizeObserver(([entry]) => {
setSize([entry.contentRect.width, entry.contentRect.height]);
}),
[],
);
const ref = useCallback(
(element: HTMLElement) => {
// Avoid observing the same target multiple times
ro.disconnect();
if (element) {
ro.observe(element);
}
},
[ro],
);
return [size, ref];
}