Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Receive dimensions of element via getBoundingClientRect in React

I would like to find out the dimensions of a DOM element in a reliable way.

My consideration was to use getBoundingClientRect for this.

  const elementRef = useRef<HTMLUListElement | null>(null);
  const [dimensions, setDimensions] = useState<DOMRect | undefined>();

  const element: HTMLUListElement | null = elementRef.current;

  /**
   * Update dimensions state.
   */
  const updateDimensions = useCallback(() => {
    if (!element) return;

    setDimensions(element.getBoundingClientRect());
  }, [element, setDimensions]);

  /**
   * Effect hook to receive dimensions changes.
   */
  useEffect(() => {
    if (element) {
      updateDimensions();
    }
  }, [element, updateDimensions]);

The problem with this approach is that useEffect only reacts to elementRef.current and not to the rect changes. Apparently the drawing of the component in the browser is not finished yet, because the dimensions are always 0.

If I do the whole thing outside the useEffect then it works. In the console I see 2 times values with 0 and then the correct dimensions.

With useEffect:

Dimensions with useEffect

Outside of useEffect:

Dimensions outside useEffect

However, I would like to save the dimensions in the state and for this I need the useEffect.

How can I achieve this with getBoundingClientRect or is there another way to do this?

SOLUTION

It was not a problem of react it self. It is a bug in ionic V5 react.

Normally you can do it in this way:

https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node

But here is an issue:

https://github.com/ionic-team/ionic/issues/19770

My solution is to use: https://github.com/que-etc/resize-observer-polyfill

import { RefCallback, useCallback, useState } from 'react';
import ResizeObserver from 'resize-observer-polyfill';


export function useDimensions(): [RefCallback<HTMLElement | null>, DOMRect | undefined] {
  const [dimensions, setDimensions] = useState<DOMRect | undefined>();

  const ref: RefCallback<HTMLElement | null> = useCallback((node: HTMLElement | null) => {
    if (node) {
      setDimensions(node.getBoundingClientRect().toJSON());

      const resizeObserver = new ResizeObserver((entries) => {
        if (entries.length) {
          setDimensions(entries[0].target.getBoundingClientRect().toJSON());
        }
      });

      resizeObserver.observe(node);

      return () => {
        resizeObserver.unobserve(node);
        resizeObserver.disconnect();
      };
    }
  }, []);

  return [ref, dimensions];
}
like image 374
Jan Bürling Avatar asked Mar 27 '20 07:03

Jan Bürling


1 Answers

you can use a callBackRef to achieve desired behaviour:

import React, { useCallback, useState } from "react";

export default function App() {
  const [dimensions, setDimensions] = useState(null);

  const callBackRef = useCallback(domNode => {
    if (domNode) {
      setDimensions(domNode.getBoundingClientRect());
    }
  }, []);

  return (
    <div>
      <h1 ref={callBackRef}>Measure me</h1>
    </div>
  );
}`
like image 121
nath354 Avatar answered Sep 19 '22 16:09

nath354