The most practical solution is to use react-measure .
Note : this code does not work with react-measure@^2.0.0 as the API changes. Follow the link above to see the new API.
import Measure from 'react-measure' const MeasuredComp = () => ( <Measure> {({width}) => <div>My width is {width}</div>} </Measure> )
To report resizing between components, you can pass the onMeasure and save the values ββit gets somewhere (the standard way to share 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> </Measure> ))
How to make your own if you really prefer:
Create a wrapper component that handles retrieving values ββfrom the DOM and listens for window resize events (or the component resize detection used by react-measure ). You tell us what details can be obtained from the DOM and provide a rendering function that takes these details as a child.
What you do must be set before the DOM details are read; when these details are not available during the initial rendering, you can use style={{visibility: 'hidden'}} so that the user cannot see it before he receives the JS-computed layout.
// @flow import React, {Component} from 'react'; import shallowEqual from 'shallowequal'; import throttle from 'lodash.throttle'; type DefaultProps = { component: ReactClass<any>, }; type Props = { domProps?: Array<string>, computedStyleProps?: Array<string>, children: (state: State) => ?React.Element<any>, component: ReactClass<any>, }; type State = { remeasure: () => void, computedStyle?: Object, [domProp: string]: any, }; export default class Responsive extends Component<DefaultProps,Props,State> { static defaultProps = { component: 'div', }; remeasure: () => void = throttle(() => { const {root} = this; if (!root) return; const {domProps, computedStyleProps} = this.props; const nextState: $Shape<State> = {}; if (domProps) domProps.forEach(prop => nextState[prop] = root[prop]); if (computedStyleProps) { nextState.computedStyle = {}; const computedStyle = getComputedStyle(root); computedStyleProps.forEach(prop => nextState.computedStyle[prop] = computedStyle[prop] ); } this.setState(nextState); }, 500); // put remeasure in state just so that it gets passed to child // function along with computedStyle and domProps state: State = {remeasure: this.remeasure}; root: ?Object; componentDidMount() { this.remeasure(); this.remeasure.flush(); window.addEventListener('resize', this.remeasure); } componentWillReceiveProps(nextProps: Props) { if (!shallowEqual(this.props.domProps, nextProps.domProps) || !shallowEqual(this.props.computedStyleProps, nextProps.computedStyleProps)) { this.remeasure(); } } componentWillUnmount() { this.remeasure.cancel(); window.removeEventListener('resize', this.remeasure); } render(): ?React.Element<any> { const {props: {children, component: Comp}, state} = this; return <Comp ref={c => this.root = c} children={children(state)}/>; } }
It is very important to respond to changes in width:
function renderColumns(numColumns: number): React.Element<any> { ... } const responsiveView = ( <Responsive domProps={['offsetWidth']}> {({offsetWidth}: {offsetWidth: number}): ?React.Element<any> => { if (!offsetWidth) return null; const numColumns = Math.max(1, Math.floor(offsetWidth / 200)); return renderColumns(numColumns); }} </Responsive> );