I am stuck with this problem regarding React.
I am building a accordion component, with animated collapsing (via max-height transition). I need the accordion to render in open state depending on "showOpen" prop.
Initially, if showOpen prop is true, the collapsible content's max-height is set to 'unset', so the content is visible. So far so good.
After that I need to get content's real dimensions and set specific max-height value, as transitions won't work with 'unset'. I am using a useLayoutEffect hook, running straight after component mount. ( I am using useLayoutEffect, as it should wait for all the dom changes (rendering children), however, it seems to works in the same way as with useEffect.
Inside the useLayoutEffect, I am not able to get content's real dimensions without the "dirty timeout". I assume, the rendering engine needs some more time to compute content's dimensions, but I thought useLayoutEffect should run after this is finished.
So far, I have tried different approaches using ResizeObserver, onLoad event and using ref callback, but none of this have worked. ResizeObserver and onLoad event were not even called, so it seems that the DOM mutations were really executed before the hook, but somehow the correct dimensions were still missing at that time.
The timeout solutions works, but seems unacceptable to me, as it depens on some magical timeout number.
Is there something I have missed ? Could you please suggest better solution ?
thank you sincerely.
const Accordion = ({ label, showOpen, children }) => {
const [isOpen, setOpenState] = useState(showOpen);
const [height, setHeight] = useState(showOpen ? 'unset' : '0px');
const [chevronRotation, setChevronRotation] = useState<'down' | 'right'>(showOpen ? 'down' : 'right');
const content = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
console.log('first effect', content?.current?.getBoundingClientRect().height); // returns 0
setTimeout(() => {
console.log(
'timeout effect',
content?.current?.getBoundingClientRect().height // returns correct height
);
}, 50);
}, []);
const toggleAccordion = (): void => {
if (isOpen) {
setOpenState(false);
setHeight('0px');
setChevronRotation('right');
} else {
setOpenState(true);
setHeight(`${filtersContent.current?.scrollHeight}px`);
setChevronRotation('down');
}
};
return (
<>
<div className={classNames(styles.accordionSection)} onClick={toggleAccordion}>
<div role="headline">{label}</div>
<Chevron width={8} fill={'#6f6f6f'} direction={chevronRotation} />
</div>
<div ref={content} style={{ maxHeight: `${height}` }}>
{children}
</div>
</>
);
};