faia20
faia20

Reputation: 2394

ReactJS - Get Height of an element

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

Upvotes: 231

Views: 525972

Answers (17)

Shuhad zaman
Shuhad zaman

Reputation: 3390

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(ref.current.clientHeight){
         setTimeout(() => {
           setHeight(ref.current.clientHeight) 
         }, 1000)
       }
      })
    
      return (
        <div ref={ref}>
          {height}
        </div>
      )
    }

Upvotes: 17

Tianxiang Ren
Tianxiang Ren

Reputation: 1

You can get the actual clientHeight by queuing the setClientHeight task into the micro queue in useEffect.

const [clientHeight, setClientHeight] = useState(0);
const containerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
  queueMicrotask(() => {
    if (containerRef.current) {
      setClientHeight(containerRef.current.clientHeight);
    }
  });
}, []);

Upvotes: 0

Asleepace
Asleepace

Reputation: 3765

This is a simple hook which waits for the component to first mount before measuring:

export function useLayout() {
  const layoutRef = useRef<HTMLDivElement>(null)
  const [isMounted, setIsMounted] = useState(false)
  const [height, setHeight] = useState(0)
  const [width, setWidth] = useState(0)

  useEffect(() => {
    setIsMounted(true)
  }, [])

  useEffect(() => {
    if (!isMounted || !layoutRef.current) return
    setHeight(layoutRef.current.clientHeight)
    setWidth(layoutRef.current.clientWidth)
  }, [isMounted])

  const isHidden = isMounted && height === 0 && width === 0

  return { isMounted, isHidden, layoutRef, height, width }
}

Example usage

export function ExampleComponent() {
  const { layoutRef, isHidden, width, height } = useLayout()

  return (
    <div ref={layoutRef}>
      {isHidden ? <Loading /> : <Graph maxWidth={width} maxHeight={height} />}
    </div>
  )
}

This fixed the following issue I was having with recharts

console.js:213 The width(0) and height(0) of chart should be greater than 0, please check the style of container, or the props width(100%) and height(100%), or add a minWidth(550) or minHeight(200) or use aspect(undefined) to control the height and width.

Upvotes: 0

jugurtha moad
jugurtha moad

Reputation: 414

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

Upvotes: 1

Mohammad Fallah
Mohammad Fallah

Reputation: 1110

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;

Upvotes: 4

Dipanjan Panja
Dipanjan Panja

Reputation: 533

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
    }
  }, []);

Upvotes: 3

jfunk
jfunk

Reputation: 8152

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;

Upvotes: 1

Ali Klein
Ali Klein

Reputation: 1908

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

Upvotes: 0

charri
charri

Reputation: 1052

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>
  )
}

Upvotes: 32

Daniele Cruciani
Daniele Cruciani

Reputation: 623

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 ).

Upvotes: 9

iamakshatjain
iamakshatjain

Reputation: 540

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.

Upvotes: 6

Andreyco
Andreyco

Reputation: 22872

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>

Upvotes: 38

Chunky Chunk
Chunky Chunk

Reputation: 17237

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
};

Upvotes: 1

Gfast2
Gfast2

Reputation: 114

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

Upvotes: 4

Paul Vincent Beigang
Paul Vincent Beigang

Reputation: 3002

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

Upvotes: 198

Mr. 14
Mr. 14

Reputation: 9528

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>
  )
}

Upvotes: 251

yoyodunno
yoyodunno

Reputation: 767

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

Upvotes: 12

Related Questions