Within ParentComponent
, I render a chart (ResponsiveLine
). I have a function (calculateHeight
) calculating the height of some DOM elements of the chart.
To work fine, my function calculateHeight
have to be triggered once the chart ResponsiveLine
is rendered.
Here's my issue: useEffect will trigger before the child is done rendering, so I can't calculate the size of the DOM elements of the chart.
How to trigger my function calculateHeight
once the chart ResponsiveLine
is done rendering?
Here's a simplified code
const ParentComponent = () => {
const myref = useRef(null);
const [marginBottom, setMarginBottom] = useState(60);
useEffect(() => {
setMarginBottom(calculateHeight(myref));
});
return (
<div ref={myref}>
<ResponsiveLine marginBottom={marginBottom}/>
</div>)
}
I can't edit the child ResponsiveLine
, it's from a library
You can use the ResizeObserver
API to track changes to the dimensions of the box of the div via its ref
(specifically the height, which is the block size dimension for content which is in a language with a horizontal writing system like English). I won't go into the details of how the API works: you can read about it at the MDN link above.
The ResponsiveLine
aspect of your question doesn't seem relevant except that it's a component you don't control and might change its state asynchronously. In the code snippet demonstration below, I've created a Child
component that changes its height after 2 seconds to simulate the same idea.
Code in the TypeScript playground
<div id="root"></div><script src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/standalone@7.18.5/babel.min.js"></script><script>Babel.registerPreset('tsx', {presets: [[Babel.availablePresets['typescript'], {allExtensions: true, isTSX: true}]]});</script>
<script type="text/babel" data-type="module" data-presets="tsx,react">
// import ReactDOM from 'react-dom/client';
// import {useEffect, useRef, useState, type ReactElement} from 'react';
// This Stack Overflow snippet demo uses UMD modules instead of the above import statments
const {useEffect, useRef, useState} = React;
// You didn't show this function, so I don't know what it does.
// Here's something in place of it:
function calculateHeight (element: Element): number {
return element.getBoundingClientRect().height;
}
function Child (): ReactElement {
const [style, setStyle] = useState<React.CSSProperties>({
border: '1px solid blue',
height: 50,
});
useEffect(() => {
// Change the height of the child element after 2 seconds
setTimeout(() => setStyle(style => ({...style, height: 150})), 2e3);
}, []);
return (<div {...{style}}>Child</div>);
}
function Parent (): ReactElement {
const ref = useRef<HTMLDivElement>(null);
const [marginBottom, setMarginBottom] = useState(60);
useEffect(() => {
if (!ref.current) return;
let lastBlockSize = 0;
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
if (!(entry.borderBoxSize && entry.borderBoxSize.length > 0)) continue;
// @ts-expect-error
const [{blockSize}] = entry.borderBoxSize;
if (blockSize === lastBlockSize) continue;
setMarginBottom(calculateHeight(entry.target));
lastBlockSize = blockSize;
}
});
observer.observe(ref.current, {box: 'border-box'});
return () => observer.disconnect();
}, []);
return (
<div {...{ref}}>
<div>height: {marginBottom}px</div>
<Child />
</div>
);
}
const reactRoot = ReactDOM.createRoot(document.getElementById('root')!);
reactRoot.render(<Parent />);
</script>