Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

149
Visualizações
React component getting wrong ref dimensions in useEffect

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>
    </>
);

};

about 4 years ago · Juan Pablo Isaza
1 Respostas
Responde à pergunta

0

I think what’s important to remember here is how the call stack/queue actually works under the hood.

setTimeout() will be placed in the job queue. Think of the job queue like a line/queue for a ride at an amusement park.

You start from the back and wait for you turn.

Async/await, for example, uses the message queue.

You can think of the message queue like the fast pass of the amusement park; it allows you to go from the exit of one ride to the FRONT of the next ride- that same ride that we just saw setTimeout() go to the back of.

So what’s happening with this code, and how does the queue stuff apply??

The layout effect hook’s behavior mixed with the job queue how it is, is quite interesting. In summary, it means that the layout effect won’t resolve/finish until after EVERYTHING else is done, since the timeout callback will be the very last thing to be run.

Mind you, the useLayoutEffect is completely blocked until after all DOM manipulations, and it fires synchronously as well.

See:

  • Node’s event loop (helps understand the idea of queues better)
  • ES6 job vs message queues

Alternatively, if it's not grabbing the ref correctly, which is odd, this seems like the better solution would be to use a plain useEffect, or even better in my opinion, using useCallback with useState to achieve the safe thing.

For example,

const [refState, setRefState] = useState<React.MutableRefObject<HTMLDivElement | null>>(null)

const cbRef = useCallback((node) => setRefState(node), node)

... 

return (
    <>
      ...
        <div ref={cbRef} style={{ maxHeight: `${height}` }}>
            {children}
        </div>
    </>
);

The ref now will trigger ONLY after the element has successfully finished mounting, and the reference will then live in the refState hook (assuming we have a [refState, setRefState] = useState(), like used in the example).

about 4 years ago · Juan Pablo Isaza Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda