212

How can I get the Height of an element after React renders that element?

HTML

<div id="container">
<!-- This element's contents will be replaced with your component. -->
<p>
jnknwqkjnkj<br>
jhiwhiw (this is 36px height)
</p>
</div>

ReactJS

var DivSize = React.createClass({

  render: function() {
    let elHeight = document.getElementById('container').clientHeight
    return <div className="test">Size: <b>{elHeight}px</b> but it should be 18px after the render</div>;
  }
});

ReactDOM.render(
  <DivSize />,
  document.getElementById('container')
);

RESULT

Size: 36px but it should be 18px after the render

It's calculating the container height before the render (36px). I want to get the height after the render. The right result should be 18px in this case. jsfiddle

Edgar
  • 6,022
  • 8
  • 33
  • 66
faia20
  • 2,204
  • 2
  • 11
  • 8
  • 1
    This is not a react question but rather a Javascript and DOM question. You should try to figure out which DOM event you should use to find the final height of your element. In the event handler, you can use `setState` to assign the height value to a state variable. – mostruash Feb 02 '16 at 12:45
  • 2
    Nowadays, I highly recommend just using react-use's useMeasure hook instead to do this. – Christopher Regner Oct 12 '20 at 09:08

16 Answers16

235

For those who are interested in using react hooks, this might help you get started.

import React, { useState, useEffect, useRef } from 'react'

export default () => {
  const [height, setHeight] = useState(0)
  const ref = useRef(null)

  useEffect(() => {
    setHeight(ref.current.clientHeight)
  })

  return (
    <div ref={ref}>
      {height}
    </div>
  )
}
Mr. 14
  • 9,228
  • 6
  • 37
  • 54
  • 23
    Thanks for your answer - it helped me get started. 2 questions though: (1) For these layout-measuring tasks, should we use `useLayoutEffect` instead of `useEffect`? The docs seem to indicate that we should. See the "tip" section here: https://reactjs.org/docs/hooks-effect.html#detailed-explanation (2) For these cases where we just need a reference to a child element, should we use `useRef` instead of `createRef`? The docs seem to indicate the `useRef` is more appropriate for this use case. See: https://reactjs.org/docs/hooks-reference.html#useref – Jason Frank Mar 04 '19 at 20:55
  • I just implemented a solution that uses the two items I'm suggesting. They seem to work well, but you may know of some downsides to these choices? Thanks for your help! – Jason Frank Mar 04 '19 at 20:55
  • 9
    @JasonFrank Good questions. 1) Both useEffect and useLayoutEffect will be fired after layout and paint. However, useLayoutEffect will fire synchronously before the next paint. I'd say if you really need visual consistency then use useLayoutEffect, otherwise, useEffect should suffice. 2) You're right about that! In a functional component createRef will create a new ref every time the component is being rendered. Using useRef is the better option. I'll update my answer. Thanks! – Mr. 14 Mar 09 '19 at 02:27
  • This helped me understand how to use a ref with hooks. I ended up going with `useLayoutEffect` after reading the other comments here. Seems to work well. :) – GuiDoody Aug 01 '19 at 14:33
  • 1
    I was setting dimensions of my component `useEffect(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight}))`, and my IDE (WebStorm) suggested me this: *ESLint: React Hook useEffect contains a call to 'setDimensions'. Without a list of dependencies, this can lead to an infinite chain of updates. To fix this, pass [] as a second argument to the useEffect Hook. (react-hooks/exhaustive-deps)*, so pass `[]` as second argument to your `useEffect` – Akshdeep Singh Oct 13 '19 at 22:27
  • I have a similar scenario but I am calling `setHeight(ref.current.clientHeight)` from a child component and applying the `height` to a sibling component as paddingTop like so, `
    `. Now to check if it works realtime I also added this line in useEffect of Child component `setTimeout(() => { ref.current.style.height = "50px" }, 2000)`. This does update the value of `height` state in parent component but the `paddingTop` is still the previous value and doesn't update?
    – Sushmit Sagar Jan 09 '20 at 17:24
  • 1
    @faia20 you may want to select this more modern answer. – doublejosh May 19 '20 at 00:41
  • @AkshdeepSingh yes, passing array argument stop the loop but, at least in my case does not really make the task: it run just once. In fact it is tricky and simple at the same time: pass an array of variables that would be checked for changes (no element, nothing changes), In my case, I use `let myself = useRef(null);` then I pass the array `[myself.current, myself.current?myself.current.clientHeight:0]` I want to know when clientHeight change, it may be strange, but it works this way. – Daniele Cruciani Jun 11 '20 at 15:36
  • Thank you! Ctrl + F for "useRef" got me here. knew it would be clean and concise – George Apr 01 '21 at 12:25
  • I'm following a similar pattern as described here however i get an error cannot read property clientHeight of null, what could be the problem? – Yashwanth somayajula May 19 '21 at 07:59
  • this leads to warning though: "ESLint: React Hook useEffect contains a call to 'setDimensions'. Without a list of dependencies, this can lead to an infinite chain of updates." – Giorgi Moniava Nov 01 '21 at 19:18
197

Following is an up to date ES6 example using a ref.

Remember that we have to use a React class component since we need to access the Lifecycle method componentDidMount() because we can only determine the height of an element after it is rendered in the DOM.

import React, {Component} from 'react'
import {render} from 'react-dom'

class DivSize extends Component {

  constructor(props) {
    super(props)

    this.state = {
      height: 0
    }
  }

  componentDidMount() {
    const height = this.divElement.clientHeight;
    this.setState({ height });
  }

  render() {
    return (
      <div 
        className="test"
        ref={ (divElement) => { this.divElement = divElement } }
      >
        Size: <b>{this.state.height}px</b> but it should be 18px after the render
      </div>
    )
  }
}

render(<DivSize />, document.querySelector('#container'))

You can find the running example here: https://codepen.io/bassgang/pen/povzjKw

Paul Vincent Beigang
  • 2,972
  • 2
  • 16
  • 31
  • 6
    This works but I am getting several eslint errors for Airbnb styleguide: `
    this.divElement = divElement}>{children}
    ` throws "[eslint] Arrow function should not return assignment. (no-return-assign)" and `this.setState({ height });` throws "[eslint] Do not use setState in componentDidMount (react/no-did-mount-set-state)". Anyone got a styleguide-compliant solution?
    – Timo Ernst Feb 21 '18 at 08:48
  • 7
    Wrap the assignment in braces so that it behaves as a function (rather than an expression), like this `ref={ divElement => {this.divElement = divElement}}` – teletypist Mar 21 '18 at 22:37
  • 4
    This sould be the accepted answer :) Besides, if you want to get the size of the element after the element is updated, you can use the method `componentDidUpdate` as well. – jjimenez Apr 04 '18 at 11:36
  • 4
    is there an alternative to calling this.setState() in componentDidMount with this example? – daniel aagentah Aug 28 '18 at 11:10
  • 4
    Good solution. But won't work for responsive designs. If header height was 50px for desktop, and user shrinks the window size, and now height becomes 100px, this solution won't take the updated height. They need to refresh the page to get the changed effects. – The Coder Feb 06 '19 at 19:41
  • 2
    @TheCoder to detect window resize: https://stackoverflow.com/questions/31661790/reactjs-get-rendered-component-height – AlexO Mar 02 '19 at 01:11
  • On mobile, this.divElement.clientHeight returns 0. Does anyone know how to resolve this on mobile? – Frazer Kirkman Mar 30 '19 at 06:06
  • 1
    TypeError: Cannot read property 'clientHeight' of undefined ---for me – diesel94 Jul 09 '21 at 09:49
37

See this fiddle (actually updated your's)

You need to hook into componentDidMount which is run after render method. There, you get actual height of element.

var DivSize = React.createClass({
    getInitialState() {
    return { state: 0 };
  },

  componentDidMount() {
    const height = document.getElementById('container').clientHeight;
    this.setState({ height });
  },

  render: function() {
    return (
        <div className="test">
        Size: <b>{this.state.height}px</b> but it should be 18px after the render
      </div>
    );
  }
});

ReactDOM.render(
  <DivSize />,
  document.getElementById('container')
);
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>

<div id="container">
<p>
jnknwqkjnkj<br>
jhiwhiw (this is 36px height)
</p>
    <!-- This element's contents will be replaced with your component. -->
</div>
RobinJ
  • 5,022
  • 7
  • 32
  • 61
Andreyco
  • 22,476
  • 5
  • 61
  • 65
32

Instead of using document.getElementById(...), a better (up to date) solution is to use the React useRef hook that stores a reference to the component/element, combined with a useEffect hook, which fires at component renders.

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

export default App = () => {
  const [height, setHeight] = useState(0);
  const elementRef = useRef(null);

  useEffect(() => {
    setHeight(elementRef.current.clientHeight);
  }, []); //empty dependency array so it only runs once at render

  return (
    <div ref={elementRef}>
      {height}
    </div>
  )
}
David Leuliette
  • 1,595
  • 18
  • 26
charri
  • 984
  • 7
  • 9
13

it might show zero. setTimeout helps to get the correct value and update the state.

import React, { useState, useEffect, useRef } from 'react'
    
    export default () => {
      const [height, setHeight] = useState(0)
      const ref= useRef(null)
    
      useEffect(() => {
       if(elemRef.current.clientHeight){
         setTimeout(() => {
           setHeight(ref.current.clientHeight) 
         }, 1000)
       }
      })
    
      return (
        <div ref={ref}>
          {height}
        </div>
      )
    }
Shuhad zaman
  • 3,156
  • 32
  • 32
12

You would also want to use refs on the element instead of using document.getElementById, it's just a slightly more robust thing.

Devid Farinelli
  • 7,514
  • 9
  • 42
  • 73
yoyodunno
  • 617
  • 1
  • 8
  • 19
  • @doublejosh basically you are right but what to do if you need to mixup for example `angularJs` and `react` while migrating from one to the other? There are cases where a direct DOM manipulation is really needed – messerbill May 19 '20 at 13:03
9

My 2020's (or 2019) answer

import React, {Component, useRef, useLayoutEffect} from 'react';
import { useDispatch } from 'react-redux';
import { Toast, ToastBody, ToastHeader } from 'reactstrap';

import {WidgetHead} from './WidgetHead';

export const Widget = ({title, toggle, reload, children, width, name}) => {
    let myself = useRef(null);
    const dispatch = useDispatch();
    useLayoutEffect(()=>{
        if (myself.current) {
            const height = myself.current.clientHeight
            dispatch({type:'GRID_WIDGET_HEIGHT', widget:name, height})
        }
    }, [myself.current, myself.current?myself.current.clientHeight:0])

    return (
        <Toast innerRef={myself}>
            <WidgetHead title={title}
                toggle={toggle}
                reload={reload} />
            <ToastBody>
            {children}
            </ToastBody>
        </Toast>
    )
}

let use your imagination for what is missing here (WidgetHead), reactstrap is something you can find on npm: replace innerRef with ref for a legacy dom element (say a <div>).

useEffect or useLayoutEffect

The last is said to be synchronous for changes

useLayoutEffect (or useEffect) second argument

Second argument is an array, and it is checked before executing the function in the first argument.

I used

[myself.current, myself.current?myself.current.clientHeight:0]

because myself.current is null before rendering, and that is a good thing not to check, the second parameter at the end myself.current.clientHeight is what I want to check for changes.

what I am solving here (or trying to solve)

I am solving here the problem of widget on a grid that change its height by their own will, and the grid system should be elastic enough to react ( https://github.com/STRML/react-grid-layout ).

Daniele Cruciani
  • 623
  • 1
  • 8
  • 15
  • 3
    great answer, but would have benefited from simpler example. Basically whiteknight's answer but with `useLayoutEffect` and actual div to tie the ref to. – bot19 Jun 25 '20 at 05:48
  • 1
    This answer almost works for me, but when I resize my window I do not see the height recalculate. – carmenism Mar 18 '22 at 04:40
  • @carmenism strange. I thought it was the contrary, it should be calculated on each rerender, but maybe you can force rendering by parameter changes. About the code I can not recall about width passed to Widget component, but maybe that is a way to force re-render the component, so fire useLayoutEffect() and recalc – Daniele Cruciani May 11 '22 at 07:32
6

Using with hooks :

This answer would be helpful if your content dimension changes after loading.

onreadystatechange : Occurs when the load state of the data that belongs to an element or a HTML document changes. The onreadystatechange event is fired on a HTML document when the load state of the page's content has changed.

import {useState, useEffect, useRef} from 'react';
const ref = useRef();
useEffect(() => {
    document.onreadystatechange = () => {
      console.log(ref.current.clientHeight);
    };
  }, []);

I was trying to work with a youtube video player embedding whose dimensions may change after loading.

iamakshatjain
  • 520
  • 6
  • 9
  • this answer and the one mentioning use of `setTimeout` are underappreciated. It seems to me `setTimeout(()=>{}, 0)` has the same effect as `document.onreadstatechange = () => {}`. Either way, I need to use either in order to get the correct width/height from `getBoundingRect()`, `clientWidth`, `offsetHeight`, etc. when trying to embed a webgl game built with Unity to a webpage – kimbaudi Jul 22 '21 at 13:54
  • @kimbaudi I have seen the wrong width being measured with the setTimeout approach as well, but it seems to happen only occasional. For me, the only stable approch so far was working with onreadystatechange – Dari4sho Feb 02 '22 at 14:17
4

Here is another one if you need window resize event:

class DivSize extends React.Component {

  constructor(props) {
    super(props)

    this.state = {
      width: 0,
      height: 0
    }
    this.resizeHandler = this.resizeHandler.bind(this);
  }

  resizeHandler() {
    const width = this.divElement.clientWidth;
    const height = this.divElement.clientHeight;
    this.setState({ width, height });
  }

  componentDidMount() {
    this.resizeHandler();
    window.addEventListener('resize', this.resizeHandler);
  }

  componentWillUnmount(){
    window.removeEventListener('resize', this.resizeHandler);
  }

  render() {
    return (
      <div 
        className="test"
        ref={ (divElement) => { this.divElement = divElement } }
      >
        Size: widht: <b>{this.state.width}px</b>, height: <b>{this.state.height}px</b>
      </div>
    )
  }
}

ReactDOM.render(<DivSize />, document.querySelector('#container'))

code pen

Gfast2
  • 114
  • 6
4

Use the useMeasure as custom hook (Typescript, SSR, hook):

import { useEffect, useRef, useState } from 'react';

interface ContainerSize {
  width: number;
  height: number;
}

type UseMeasureArgs = () => {
  ref: React.RefObject<HTMLDivElement>;
  size: ContainerSize;
  windowSize: ContainerSize;
};

const initSize: ContainerSize = { width: 0, height: 0 };

const useMeasure: UseMeasureArgs = () => {
  const ref = useRef<HTMLDivElement>(null);
  const [size, setSize] = useState<ContainerSize>(initSize);
  const [windowSize, setWindowSize] = useState<ContainerSize>(initSize);

  useEffect(() => {
    if (ref.current) {
      setSize({ width: ref.current.offsetWidth, height: ref.current.offsetHeight });
    }
    if (typeof window !== 'undefined') {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }
  }, []);

  return { ref, size, windowSize };
};

export default useMeasure;

Mohammad Fallah
  • 976
  • 11
  • 14
3

you can also use getBoundingClientRect() to get height, width.

const [width, setWidth] = useState(0);

useEffect(() => {
    const element = document.getElementById('element-id');
    if (element) {
      setWidth(element.getBoundingClientRect().width); // or height
    }
  }, []);
Dipanjan Panja
  • 396
  • 4
  • 12
1

An alternative solution, in case you want to retrieve the size of a React element synchronously without having to visibly render the element, you can use ReactDOMServer and DOMParser.

I use this function to get the height of a my list item renderer when using react-window (react-virtualized) instead of having to hardcode the required itemSize prop for a FixedSizeList.

utilities.js:

/**
 * @description Common and reusable functions 
 * 
 * @requires react-dom/server
 * 
 * @public
 * @module
 * 
 */
import ReactDOMServer from "react-dom/server";

/**
 * @description Retrieve the width and/or heigh of a React element without rendering and committing the element to the DOM.
 * 
 * @param {object} elementJSX - The target React element written in JSX.
 * @return {object} 
 * @public
 * @function
 * 
 * @example
 * 
 * const { width, height } = getReactElementSize( <div style={{ width: "20px", height: "40px" }} ...props /> );
 * console.log(`W: ${width}, H: ${height});  // W: 20, H: 40
 * 
 */
const getReactElementSize = (elementJSX) => {

    const elementString = ReactDOMServer.renderToStaticMarkup(elementJSX);
    const elementDocument = new DOMParser().parseFromString(elementString, "text/html");
    const elementNode = elementDocument.getRootNode().body.firstChild;

    const container = document.createElement("div");
    const containerStyle = {

        display: "block",
        position: "absolute",
        boxSizing: "border-box",
        margin: "0",
        padding: "0",
        visibility: "hidden"
    };

    Object.assign(container.style, containerStyle);

    container.appendChild(elementNode);
    document.body.appendChild(container);

    const width = container.clientWidth;
    const height = container.clientHeight;

    container.removeChild(elementNode);
    document.body.removeChild(container);

    return {

        width,
        height
    };
};

/**
 * Export module
 * 
 */
export {

    getReactElementSize
};
Chunky Chunk
  • 16,553
  • 15
  • 84
  • 162
1

I found the other answers with React hooks were not updating properly upon resize.

After searching around I found this blog post that gives a working React hook that observes resize events:

The TL;DR is here:

npm install --save resize-observer-polyfill

// useResizeObserver.js
import { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import ResizeObserver from 'resize-observer-polyfill';

const useObserver = ({ callback, element }) => {

  const current = element && element.current;

  const observer = useRef(null);

  useEffect(() => {
      // if we are already observing old element
      if (observer && observer.current && current) {
        observer.current.unobserve(current);
      }
      const resizeObserverOrPolyfill =  ResizeObserver;
      observer.current = new resizeObserverOrPolyfill(callback);
      observe();

      return () => {
        if (observer && observer.current && element &&
           element.current) {
          observer.current.unobserve(element.current);
        }
      };
  }, [current]);

  const observe = () => {
    if (element && element.current && observer.current) {
      observer.current.observe(element.current);
    }
  };

};

useObserver.propTypes = {
  element: PropTypes.object,
  callback: PropTypes.func,
};

export default useObserver;

Then an example usage in a component:

// shape.js
import React, { useEffect, useState, useRef } from 'react';
import useResizeObserver from 'path/to/useResizeObserver.js';

const Shape = () => {
  const [height, setHeight] = useState(0);
  const svgRef = useRef(null);

  const doHeightAdjustment = () => {
    setHeight(svgRef.current.clientHeight);
  };

  useResizeObserver({callback: doHeightAdjustment, element: svgRef});

  return (
    <div ref={svgRef} style={{ height: '100vh' }}>
      {height}
    </div>
  );
};

export default Shape;

jfunk
  • 7,176
  • 4
  • 37
  • 38
1

You can use this hook

import useMeasure from "react-use-measure";
const [ref, {height}] = useMeasure()

the rest of your code

<div ref={ref} id="container">
</div>

then you can access the height where you want and it is updated every time its size is changing

jugurtha moad
  • 304
  • 3
  • 5
0

I found useful npm package https://www.npmjs.com/package/element-resize-detector

An optimized cross-browser resize listener for elements.

Can use it with React component or functional component(Specially useful for react hooks)

qmn1711
  • 822
  • 9
  • 22
0

Here's a nice reusable hook amended from https://swizec.com/blog/usedimensions-a-react-hook-to-measure-dom-nodes:

import { useState, useCallback, useEffect } from 'react';

function getDimensionObject(node) {
  const rect = node.getBoundingClientRect();

  return {
    width: rect.width,
    height: rect.height,
    top: 'x' in rect ? rect.x : rect.top,
    left: 'y' in rect ? rect.y : rect.left,
    x: 'x' in rect ? rect.x : rect.left,
    y: 'y' in rect ? rect.y : rect.top,
    right: rect.right,
    bottom: rect.bottom
  };
}

export function useDimensions(data = null, liveMeasure = true) {
  const [dimensions, setDimensions] = useState({});
  const [node, setNode] = useState(null);

  const ref = useCallback(node => {
    setNode(node);
  }, []);

  useEffect(() => {
    if (node) {
      const measure = () =>
        window.requestAnimationFrame(() =>
          setDimensions(getDimensionObject(node))
        );
      measure();

      if (liveMeasure) {
        window.addEventListener('resize', measure);
        window.addEventListener('scroll', measure);

        return () => {
          window.removeEventListener('resize', measure);
          window.removeEventListener('scroll', measure);
        };
      }
    }
  }, [node, data]);

  return [ref, dimensions, node];
}

To implement:

import { useDimensions } from '../hooks';

// Include data if you want updated dimensions based on a change.
const MyComponent = ({ data }) => {
  const [
    ref,
    { height, width, top, left, x, y, right, bottom }
  ] = useDimensions(data);

  console.log({ height, width, top, left, x, y, right, bottom });

  return (
    <div ref={ref}>
      {data.map(d => (
        <h2>{d.title}</h2>
      ))}
    </div>
  );
};

Ali Klein
  • 1,811
  • 13
  • 13