How to Get a React Component's Size (Height/Width) Before Render

How to get a react component's size (height/width) before render?

The example below uses react hook useEffect.

Working example here

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

const ComponentWithDimensions = props => {
const targetRef = useRef();
const [dimensions, setDimensions] = useState({ width:0, height: 0 });

useLayoutEffect(() => {
if (targetRef.current) {
setDimensions({
width: targetRef.current.offsetWidth,
height: targetRef.current.offsetHeight
});
}
}, []);

return (
<div ref={targetRef}>
<p>{dimensions.width}</p>
<p>{dimensions.height}</p>
</div>
);
};

export default ComponentWithDimensions;

Some Caveats

useEffect will not be able to detect it's own influence to width and height

For example if you change the state hook without specifying initial values (eg const [dimensions, setDimensions] = useState({});), the height would read as zero when rendered because

  • no explicit height was set on the component via css
  • only content drawn before useEffect can be used to measure width and height
  • The only component contents are p tags with the height and width variables, when empty will give the component a height of zero
  • useEffect will not fire again after setting the new state variables.

This is probably not an issue in most use cases, but I thought I would include it because it has implications for window resizing.

Window Resizing

I also think there are some unexplored implications in the original question. I ran into the issue of window resizing for dynamically drawn components such as charts.

I'm including this answer even though it wasn't specified because

  1. It's fair to assume that if the dimensions are needed by the application, they will probably be needed on window resize.
  2. Only changes to state or props will cause a redraw, so a window resize listener is also needed to monitor changes to the dimensions
  3. There's a performance hit if you redraw the component on every window resize event with more complex components. I found
    introducing setTimeout and clearInterval helped. My component
    included a chart, so my CPU spiked and the browser started to crawl.
    The solution below fixed this for me.

code below, working example here

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

const ComponentWithDimensions = (props) => {
const targetRef = useRef();
const [dimensions, setDimensions] = useState({});

// holds the timer for setTimeout and clearInterval
let movement_timer = null;

// the number of ms the window size must stay the same size before the
// dimension state variable is reset
const RESET_TIMEOUT = 100;

const test_dimensions = () => {
// For some reason targetRef.current.getBoundingClientRect was not available
// I found this worked for me, but unfortunately I can't find the
// documentation to explain this experience
if (targetRef.current) {
setDimensions({
width: targetRef.current.offsetWidth,
height: targetRef.current.offsetHeight
});
}
}

// This sets the dimensions on the first render
useLayoutEffect(() => {
test_dimensions();
}, []);

// every time the window is resized, the timer is cleared and set again
// the net effect is the component will only reset after the window size
// is at rest for the duration set in RESET_TIMEOUT. This prevents rapid
// redrawing of the component for more complex components such as charts
window.addEventListener('resize', ()=>{
clearInterval(movement_timer);
movement_timer = setTimeout(test_dimensions, RESET_TIMEOUT);
});

return (
<div ref={ targetRef }>
<p>{ dimensions.width }</p>
<p>{ dimensions.height }</p>
</div>
);
}

export default ComponentWithDimensions;

re: window resizing timeout - In my case I'm drawing a dashboard with charts downstream from these values and I found 100ms on RESET_TIMEOUT seemed to strike a good balance for me between CPU usage and responsiveness. I have no objective data on what's ideal, so I made this a variable.

React.js - Is it possible to get the size of an element before the first render?

I'm trying to find a way to calculate the element's width before it reaches to the DOM

You can't.

Because of the styles that can be added on your element, you can't calculate the width before you add it to the dom.

But there is some solutions you can try.

You should use useLayoutEffect.

The signature is identical to useEffect, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.

These means you can calculate the width before the browser paint the element on the DOM.

React: To know size of the element before it gets rendered

The answer is no, but please read for ideas how to cope.

If the DOM element doesn't exist, you can't find its dimensions. The DOM element doesn't exist until the first render for each component has been done. You can't know anything about this DOM element or its parent until the component's componentDidMount() is called.

Note also the order that components are rendered. On the initial rendering of a tree/chain of components, the child's componentDidMount() will be called before the parent's componentDidMount() ! Here's a snapshot of logs from a nested menu tree:

(MenuItemSubmenu:handleRefSet)          appMenu:2.2.5.4   handleRefSet()
(MenuItemSubmenu:componentDidMount) appMenu:2.2.5.4 componentDidMount()
(MenuPane:handleRefSet) appMenu:2.2.5 handleRefSet()
(MenuPane:componentDidMount) appMenu:2.2.5 componentDidMount()
(MenuItemSubmenu:handleRefSet) appMenu:2.2.5 handleRefSet()
(MenuItemSubmenu:componentDidMount) appMenu:2.2.5 componentDidMount()
(MenuPane:handleRefSet) appMenu:2.2 handleRefSet()
(MenuPane:componentDidMount) appMenu:2.2 componentDidMount()
(MenuItemSubmenu:handleRefSet) appMenu:2.2 handleRefSet()
(MenuItemSubmenu:componentDidMount) appMenu:2.2 componentDidMount()

componentDidMount() for child item 2.2.5.4 is called before that for its parent item 2.2.5. From that we can see that the parent component can't know its own DOM element until all processing for the child has completed. Um, wow!

However, pursuing your question, I added some logging to a component's componentDidMount() method. I logged this.refToOurDom.parentElement and found that it was defined and looked good!

(MenuItemSubmenu:handleRefSet)  appMenu:2.2.5.4   parent dims:  height 76   width 127

So the answer to your question is, no, your DOM element doesn't exist yet in componentWillMount() and the parent doesn't exist either on initial render. You must wait until componentDidMount() is called before finding out any component's dimensions.

But please note the trick that react-dimensions uses. Perhaps that will give you ideas how to structure your code.

In its render() method react-dimensions may not render the enclosed component! On the initial render it only renders the containing <div>. Only after its componentDidMount() is called and it finds out the parent's dimensions, only then will the enclosed component be rendered, and now supplying the parent's dimensions.

So another answer is just don't render your component's contents until you know the dimensions of the parent. Using react-dimensions makes that easy, because you aren't even rendered until the dimensions are known. But you could also do the same sort of thing, getting the reference in your componentDidMount() and grabbing dimensions, and simply putting an if(){} around rendering the real contents of your component until you know the dimensions. (You'll need at least an outer <div> to get your own ref)

ReactJS - Get Height of an element

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>

Get viewport/window height in ReactJS

class AppComponent extends React.Component {

constructor(props) {
super(props);
this.state = {height: props.height};
}

componentWillMount(){
this.setState({height: window.innerHeight + 'px'});
}

render() {
// render your component...
}
}

Set the props

AppComponent.propTypes = {
height:React.PropTypes.string
};

AppComponent.defaultProps = {
height:'500px'
};

viewport height is now available as {this.state.height} in rendering template

How to get element height and width from ReactNode?

You can achieve this by using React.Children to dynamically build up a list of references before rendering the children. If you have access to the children element references, you can follow the below approach. If you don't then you can follow the bit at the bottom.

You have access to the children element references

If the children components pass up their element reference, you can use React.Children to loop through each child and get each element reference. Then use this to perform calculations before the children components are rendered.

i.e. This is a very simple example on how to retrieve the references and use them.

interface LayoutWrapperProps {
onMount: () => void;
}

const LayoutWrapper: React.FC<LayoutWrapperProps> = ({ onMount, children }) => {
React.useEffect(() => {
onMount();
}, [onMount]);

return <>{children}</>;
};

const Layout: React.FC = ({ children }) => {
const references = React.useRef<HTMLElement[]>([]);

React.useEffect(() => {
references.current = [];
});

function getReference(ref: HTMLElement) {
references.current = references.current.filter(Boolean).concat(ref);
}

function getHeights() {
const heights = references.current.map((ref) =>
ref?.getBoundingClientRect()
);
console.log(heights);
}

const clonedChildren = React.Children.map(children, (child) => {
return React.cloneElement(child as any, {
ref: getReference
});
});

return <LayoutWrapper onMount={getHeights}>{clonedChildren}</LayoutWrapper>;
};

If you don't have access to the children element references

If the children components aren't passing up an element as the reference, you'll have to wrap the dynamic children components in a component so we can get an element reference. i.e.

const WrappedComponent = React.forwardRef((props, ref) => {
return (
<div ref={ref}>
{props.children}
</div>
)
});

When rendering the children components, then the code above that gets the references will work:

<Layout>
<WrappedComponent>
<Child1 />
</WrappedComponent>
</Layout>

find size of react component or image before rendering

Images are downloaded asynchronously while the javascript continues to execute, so you need to wait for them to load in order to record their heights. You could wait on their onload event and do something like

 let imgHeights = {};
images.forEach(imgUrl => {
let img = new Image();
img.src = imgUrl;
img.onload = () => imgHeights[imgUrl] = img.height;
})

That just gets you started; you still have to figure out how to render them as they become ready.



Related Topics



Leave a reply



Submit