You can use ref callbacks to measure the rendered size of React components, did you know? It's a neat little trick.
Here's how it works 👇 1. React renders your component 2. Browser layout engine does its thing 3. ref callback fires 4. Use getBoundingClientRect
to measure element size 5. Use this info for whatever you want
We used this trick in this Dynamic SVG accordion example and in this Tiny React & D3 flamegraph tutorial. That's because layouting in SVG is hard, and you have to do everything yourself.
Using ref callbacks to measure your elements is a little less useful in the modern HTML + CSS world. You can use flexbox and css-grid so you never need to know what you're dealing with.
And yet when push comes to shove, sometimes you just really need your code to know the size of an element.
A minimal size reporting component looks like this 👇
class ReportSize extends React.Component {
refCallback = (element) => {
if (element) {
this.props.getSize(element.getBoundingClientRect());
}
};
render() {
return (
<div ref={this.refCallback} style={{ border: "1px solid red" }}>
{faker.lorem.paragraphs(Math.random() * 10)}
</div>
);
}
}
The render
method outputs a <div>
with a ref callback and a red border. Inside, we use faker
to generate up to 10 random paragraphs.
After React places this element, it calls refCallback
with a reference to the rendered DOM node. We can then use getBoundingClientRect
to measure its size.
{
"x": 8,
"y": 158.8125,
"width": 544,
"height": 340,
"top": 158.8125,
"right": 552,
"bottom": 498.8125,
"left": 8
}
All sorts of useful info!
So why not just use componentDidMount
?
Yes, that works too. But it's less elegant because you have to save the ref
first. The refCallback
API calls your function with a nice reference already packaged in.
However, you might still have to do that if your component size changes after initial render. Observe 👇
Clicking the shuffle
button doesn't report new sizing information up the hierarchy. That's not good 🤔
If your component changes size without re-mounting, you have to re-measure its size in componentDidUpdate
as well. But that way lies trouble… you can fall into the infinite recursion trap.
You can solve the problem with a lock, like this 👇
Keep clicking shuffle
and sizing info is always correct.
The key is enabling size reporting when you logically know size is going to change, in shuffle
, and disabling it as soon as you report the change in componentDidUpdate
.
shuffle = () => {
this.doReportSize = true;
this.setState({
text: faker.lorem.paragraphs(Math.random() * 10)
});
};
refCallback = element => {
if (element) {
this.elementRef = element;
this.props.getSize(element.getBoundingClientRect());
}
};
componentDidUpdate() {
if (this.doReportSize) {
this.props.getSize(this.elementRef.getBoundingClientRect());
this.doReportSize = false;
}
}
Oof, not pretty. Setting a this.elementRef
in our callback, messing around with class properties for flags. Pretty sure we could've just used the new React.createRef()
API in combination with componentDidMount
and componentDidUpdate
.
Would still need the flag to prevent infinite loops, however.
I wonder if using class properties to make render flags like that will continue to work when asynchronous rendering comes with React 17 🤔
Continue reading about Use ref callbacks to measure React component size
Semantically similar articles hand-picked by GPT-4
- useDimensions – a React Hook to measure DOM nodes
- Trying the new ResizeObserver and IntersectionObserver APIs
- Declarative D3 transitions with React 16.3
- Declarative D3 charts with React 16.3
- Easy D3 blackbox components with React hooks
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️