0

I am working with this react custom hook to get the dimensions of the current element by useRef(). It works but It keeps re-rendering the element and I cannot find why, because ref is added to the dependency array of the useEffect hook..

enter image description here

import React, { useState, createRef } from 'react'

const useRefDimensions = (ref) => {
  const [dimensions, setDimensions] = useState({ width: 1, height: 2 })
  React.useEffect(() => {
    if (ref.current) {
      const { current } = ref
      const boundingRect = current.getBoundingClientRect()
      const { width, height } = boundingRect
      setDimensions({ width: Math.round(width), height: Math.round(height) })
    }
  }, [ref])
  return dimensions
}

export default function Home() {
  const divRef = createRef()
  const dimensions = useRefDimensions(divRef)

  return (
    <div style={{ height: '100vh', width: '100vw' }}>
      <div
        ref={divRef}
        style={{
          margin: '50px',
          width: '70%',
          height: '70%',
          border: '1px solid black',
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center',
        }}
      >
        Dimensions: {dimensions.width}w {dimensions.height}h
      </div>
    </div>
  )
}
2
  • Are you sure that this is what's causing your re-renders? I just copied your code into a codepen and not getting any errors. codepen.io/forbesd7/pen/oNqEwwN?editors=1111 The ref isn't changing so it shouldn't be re-rendering...I also see in your errors that GardenCanvas is causing the error (But maybe you just changed the name for this example) Commented Aug 2, 2022 at 11:23
  • Strange.. With my setup I get errors. I accidentally used a different screenshot from my actual project which gives the same errors as this prototype code. Commented Aug 2, 2022 at 12:03

3 Answers 3

3

There are a couple of things to consider here:

1 - Closures: createRef is recreated at each render so the function created on render 1 will not have deep equality with function created on render 2.

2 - Using refs as dependency array: Since changing a ref value does not make react component to re-render, it does not have too much sense to add it into a dependency array.

Here you have a couple of resources that will definitely help you udnerstand better whats wrong there:

https://tkdodo.eu/blog/hooks-dependencies-and-stale-closures

https://epicreact.dev/why-you-shouldnt-put-refs-in-a-dependency-array/

Sign up to request clarification or add additional context in comments.

Comments

1

Your code is incorrect at all, you can't keep track of element size changes like this. Right now it will re-render indefinitely because at each render a new instance of ref will be created for divRef and only the link divRef.current will be permanent - so for an fast fix you can do smtn like this:

import React, { useState, createRef } from 'react'

const useRefDimensions = (ref) => {
  const [dimensions, setDimensions] = useState({ width: 1, height: 2 })
  React.useEffect(() => {
    if (ref.current) {
      const { current } = ref
      const boundingRect = current.getBoundingClientRect()
      const { width, height } = boundingRect
      setDimensions({ width: Math.round(width), height: Math.round(height) })
    }
  }, [ref.current])
  return dimensions
}

export default function Home() {
  const divRef = createRef()
  const dimensions = useRefDimensions(divRef)

  return (
    <div style={{ height: '100vh', width: '100vw' }}>
      <div
        ref={divRef}
        style={{
          margin: '50px',
          width: '70%',
          height: '70%',
          border: '1px solid black',
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center',
        }}
      >
        Dimensions: {dimensions.width}w {dimensions.height}h
      </div>
    </div>
  )
}

but it will work only for the first time, so here is the same functionality code:

import React, { useState, createRef } from 'react'

const useRefDimensions = (ref) => {
  const [dimensions, setDimensions] = useState({ width: 1, height: 2 })
  React.useEffect(() => {
    if (ref.current) {
      const { current } = ref
      const boundingRect = current.getBoundingClientRect()
      const { width, height } = boundingRect
      setDimensions({ width: Math.round(width), height: Math.round(height) })
    }
  }, [])
  return dimensions
}

export default function Home() {
  const divRef = createRef()
  const dimensions = useRefDimensions(divRef)

  return (
    <div style={{ height: '100vh', width: '100vw' }}>
      <div
        ref={divRef}
        style={{
          margin: '50px',
          width: '70%',
          height: '70%',
          border: '1px solid black',
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center',
        }}
      >
        Dimensions: {dimensions.width}w {dimensions.height}h
      </div>
    </div>
  )
}

but if you want to keep track of element size changes - I recommend you to use libraries like this one: https://www.npmjs.com/package/element-resize-detector

Comments

0
import React, { useState, useCallback } from "react";
export function useRefDimensions() {
  const [dimensions, setDimensions] = useState({ width: 1, height: 2   });
  const ref = useCallback((node) => {
if (node) {
  const boundingRect = node.getBoundingClientRect();
  const { width, height } = boundingRect;
  setDimensions({ width: Math.round(width), height: Math.round(height)  });
  const resizeObserver = new ResizeObserver((entries) => {
    if (entries.length) {
      const boundingRect = node.getBoundingClientRect();
      const { width, height } = boundingRect;
      setDimensions({
        width: Math.round(width),
        height: Math.round(height),
      });
    }
  });
  resizeObserver.observe(node);
  return () => {
    resizeObserver.unobserve(node);
    resizeObserver.disconnect();
    };
   }
 }, []);
 return { ref, dimensions };
}
export default function Home() {
const { ref, dimensions } = useRefDimensions();
 return (
  <div style={{ height: "100vh", width: "100vw" }}>
    <div
    ref={ref}
    style={{
      margin: "50px",
      width: "70%",
      height: "70%",
      border: "1px solid black",
      display: "flex",
      justifyContent: "center",
      alignItems: "center",
    }}
   >
     Dimensions: {dimensions.width}w {dimensions.height}h
   </div>
 </div>
 );
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.