Swizec Teller - a geek with a hatswizec.com

Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Senior Engineer Mindset cover
Learn more

    Use ref callbacks to measure React component size

    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 πŸ€”

    Did you enjoy this article?

    Published on June 7th, 2018 in Front End, Technical,

    Senior Mindset Book

    Get promoted, earn a bigger salary, work for top companies

    Learn more

    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

    Want to brush up on modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

    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 ❀️

    Created by Swizec with ❀️