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
This question is related to
javascript
reactjs
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)
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'))
You would also want to use refs on the element instead of using document.getElementById
, it's just a slightly more robust thing.
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>
);
};
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
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.
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>
).
The last is said to be synchronous for changes
useLayoutEffect
(or useEffect
) second argumentSecond 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.
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 ).
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>
)
}
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>
)
}
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>
)
}
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
};
Source: Stackoverflow.com