Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically track width/height of div in React.js

I need a help with my work task: I have some div block with following styles:

.main-div {
  width: 500px;
  height: 400px;
  border: 1px solid #000;
  resize: both;
  overflow: hidden;
}

And some React Component:

import React, { useState } from "react";

export const SomeComponent = () => {
  const [width, setWidth] = useState();
  const [height, setHeight] = useState();

  return (
    <div className="main-div">
     <p>Block width: {width}, height: {height} </p>
    </div>
  )
}

How can I get dynamically width/height of resizing div? I mean i have to keep track of the block dimension in the process of resizing.

like image 914
Adam Bess Avatar asked Oct 24 '25 17:10

Adam Bess


1 Answers

EDIT: as @AliAkbarAzizi pointed out (thank you!), my first answer had a bug in the usage of the width and height states inside the callback for the ResizeObserver. For clarity, I updated here the answer to make it the most correct possible. I will also provide a more in-depth explanation of what went wrong and why below for anyone interested.

SHORT UPDATED VERSION

You could combine ResizeObserver, useEffect, and useRef:

  • ResizeObserver allows you to observe a HTML element and to execute an event handler every time it gets resized;
  • useEffect is executed right after the component mounted, so it is the right place to attach the observer to your div, and to disconnect it when the component is going to unmount via the returned function;
  • useRef creates a reference through to a child.
import React, { useState, useEffect, useRef } from "react";

export const SomeComponent = () => {
  const [width, setWidth] = useState();
  const [height, setHeight] = useState();

  // useRef allows us to "store" the div in a constant, 
  // and to access it via observedDiv.current
  const observedDiv = useRef(null);

  // ref for intermediate values
  const sizesRef = useRef({width: -1, height: -1});

  useEffect(() => {
    if (!observedDiv.current) {
      // we do not initialize the observer unless the ref has
      // been assigned
      return;
    }

    // we also instantiate the resizeObserver and we pass
    // the event handler to the constructor
    const resizeObserver = new ResizeObserver(() => {
      if(observedDiv.current.offsetWidth !== sizesRef.current.width) {
        sizesRef.current.width = observedDiv.current.offsetWidth;
        setWidth(observedDiv.current.offsetWidth); 
      }
      if(observedDiv.current.offsetHeight !== sizesRef.current.height) {
        sizesRef.current.height = observedDiv.current.offsetHeight;
        setHeight(observedDiv.current.offsetHeight);
      }
    });
    
    // the code in useEffect will be executed when the component
    // has mounted, so we are certain observedDiv.current will contain
    // the div we want to observe
    resizeObserver.observe(observedDiv.current);


    // if useEffect returns a function, it is called right before the
    // component unmounts, so it is the right place to stop observing
    // the div
    return (() => {
      resizeObserver.disconnect();
    });
  },
  // only update the effect if the ref element changed
  [observedDiv.current])

  return (
    <div className="main-div" ref={observedDiv}>
     <p>Block width: {width}, height: {height} </p>
    </div>
  )
}

Going deeper...

The example I provided in my first answer used the useState() hook to store width and height and print them inside the <p> tag of the component. There is nothing inherently wrong with this, however in the old answer I also accessed the values of the state directly inside the ResizeObserver callback:

import React, { useState, useEffect, useRef } from "react";

export const SomeComponent = () => {
  const [width, setWidth] = useState();
  const [height, setHeight] = useState();

  const observedDiv = useRef(null);

  useEffect(() => {
    if (!observedDiv.current) {
      return;
    }

    const resizeObserver = new ResizeObserver(() => {
      if(observedDiv.current.offsetWidth !== width) { // HERE
        setWidth(observedDiv.current.offsetWidth); 
      }
      if(observedDiv.current.offsetHeight !== height) { //HERE
        setHeight(observedDiv.current.offsetHeight);
      }
    });
    resizeObserver.observe(observedDiv.current);

    return function cleanup() {
      resizeObserver.disconnect();
    }
  },
  [observedDiv.current])

  return (
    <div className="main-div" ref={observedDiv}>
     <p>Block width: {width}, height: {height} </p>
    </div>
  )
}

There lies the problem: I wanted to use the state to print out the values to the JSX and to check for differences between renders so not to retrigger the render in case of duplicate values.

As @AliAkbarAzizi noticed, those values were never updated after the first render: this is a case of something usually know as stale closure. Usually, as their comment correctly states, adding those values to the dependency array is the correct approach to solving the issue, something often highlighted by common react linters. There are some istances, however, in which adding the values directly to the dependency array of the hook is not desirable: in this case, it would re-spawn a ResizeObserver at each size fluctuation.

There are a few ways of dealing with this:

  • we could use an updater function, if the value of the new state depends on the previous one; or
  • we could use refs which are explicitly designed to store variables that do not trigger re-renders (which is the solution I opted for in the updated answer).

But why did you put the variables in the state in the first place?

Because this is just an example and I wanted a clear way to show to the user that the values update with the resizing of the container. Coincidentally, the bug did not affect the final result that much, however it is important to note that it is unlikely that those values will have a role in triggering re-renders in a real use case (although, never say never), meaning that while using the dependency array is the correct solution most of the times, in this scenario using refs is the correct approach. I would also like to point out that, for the majority of cases, checking that the new state value differs from the old one is not needed since react handles it automatically by using the Object.is comparison, so this check is only actually useful in a few edge cases or if one needs custom logic (for example, only trigger the re-render for even width values).

\

Sources

  1. ResizeObserver:
    • API
    • Browser compatibility
  2. useEffect hook
  3. useRef hook
  4. useEffect hook and updater functions
like image 177
albjerto Avatar answered Oct 26 '25 07:10

albjerto



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!