Steve Hollasch Steve Hollasch - 1 month ago 13
React JSX Question

How can I respond to the width of an auto-sized DOM element in React?

I have a complex web page using React components, and am trying to convert the page from a static layout to a more responsive, resizable layout. However, I keep running into limitations with React, and am wondering if there's a standard pattern for handling these issues. In my specific case, I have a component that renders as a div with display:table-cell and width:auto.

Unfortunately, I cannot query the width of my component, because you can't compute the size of an element unless it's actually placed in the DOM (which has the full context with which to deduce the actual rendered width). Besides using this for things like relative mouse positioning, I also need this to properly set width attributes on SVG elements within the component.

In addition, when the window resizes, how do I communicate size changes from one component to another during setup? We're doing all of our 3rd-party SVG rendering in shouldComponentUpdate, but you cannot set state or properties on yourself or other child components within that method.

Is there a standard way of dealing with this problem using React?


The most practical solution is to use react-measure:

import Measure from 'react-measure'

const MeasuredComp = () => (
    {({width}) => <div>My width is {width}</div>}

To communicate size changes between components, you can pass an onMeasure callback and store the values it receives somewhere (the standard way of sharing state these days is to use Redux):

import Measure from 'react-measure'
import connect from 'react-redux'
import {setMyCompWidth} from './actions' // some action that stores width in somewhere in redux state

function select(state) {
  return {
    currentWidth: ... // get width from somewhere in the state

const MyComp = connect(select)(({dispatch, currentWidth}) => (
  <Measure onMeasure={({width}) => dispatch(setMyCompWidth(width))}>
    <div>MyComp width is {currentWidth}</div>

How to roll your own if you really prefer to:

Create a wrapper component that handles getting values from the DOM and listening to window resize events (or component resize detection as used by react-measure). You tell it which props to get from the DOM and provide a render function taking those props as a child.

What you render has to get mounted before the DOM props can be read; when those props aren't available during the initial render, you might want to use style={{visibility: 'hidden'}} so that the user can't see it before it gets a JS-computed layout.

/* @flow */

import React, {Component} from 'react';
import shallowEqual from 'fbjs/lib/shallowEqual';
import _ from 'lodash';

type Props = {
  domProps?: string[],
  computedStyleProps?: string[],
  children: (state: {computedStyle?: Object, [domProp: string]: any}) => ?React.Element,
  component: string

type DefaultProps = {
  component: string

type State = Object;

export default class Responsive extends Component<DefaultProps,Props,State> {
  static defaultProps = {
    component: 'div'
  state: State = {
    remeasure: this.remeasure
  mounted: boolean = false;
  root: ?Object;
  componentWillMount() {
    this.mounted = true;
  componentDidMount() {
    window.addEventListener('resize', this.remeasure);
  componentWillReceiveProps(nextProps: Props) {
    if (!shallowEqual(this.props.domProps, nextProps.domProps) || 
        !shallowEqual(this.props.computedStyleProps, nextProps.computedStyleProps)) {
  componentWillUnmount() {
    this.mounted = false;
    window.removeEventListener('resize', this.remeasure);
  remeasure: Function = _.throttle(() => {
    const {root} = this;
    if (this.mounted && root) {
      let {domProps, computedStyleProps} = this.props;
      let nextState = {};
      if (domProps) {
        domProps.forEach(prop => nextState[prop] = root[prop]);
      if (computedStyleProps) {
        nextState.computedStyle = {};
        let computedStyle = getComputedStyle(root);
        computedStyleProps.forEach(prop => nextState.computedStyle[prop] = computedStyle[prop]);
  }, 500);
  render(): ?React.Element {
    let {props: {children}, state} = this;
    let Comp: any = this.props.component;
    return <Comp ref={c => this.root= c} children={children(state)}/>;

With this, responding to width changes is very simple:

function renderColumns(numColumns: number): React.Element {
const responsiveView = <Responsive domProps={['offsetWidth']}>
  {({offsetWidth}) => {
    let numColumns = offsetWidth ? Math.max(1, Math.floor(offsetWidth / 200));
    return offsetWidth ? renderColumns(numColumns) : null;