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

I have a complex web page using React components, and I'm trying to convert a page from a static layout to a more responsive, resizable one. However, I constantly run into limitations with React, and I wonder if there is a standard template to solve these problems. In my specific case, I have a component that displays as a div with the display: table-cell and width: auto.

Unfortunately, I cannot ask for the width of my component because you cannot calculate the size of an element unless it is actually placed in the DOM (which has a full context with which you can display the actual rendered width). Besides using this for things like relative mouse positioning, I also need this to set width attributes correctly on SVG elements in the component.

Also, when a window is resized, how do I report resize from one component to another during installation? We do all our third-party SVG rendering in shouldComponentUpdate, but you cannot set state or properties on yourself or on other child components in this method.

Is there a standard way to solve this problem with React?

+79
javascript reactjs responsive
Aug 18 '14 at
source share
4 answers

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> ); 
+59
Oct 08 '15 at 23:10
source share

I think the lifecycle method you are looking for is componentDidMount . Elements are already hosted in the DOM, and you can get information about them from the refs component.

For example:

 var Container = React.createComponent({ componentDidMount: function () { // if using React < 0.14, use this.refs.svg.getDOMNode().offsetWidth var width = this.refs.svg.offsetWidth; }, render: function () { <svg ref="svg" /> } }); 
+41
Aug 20 '14 at 16:44
source share

As an alternative to couchand solution, you can use findDOMNode

 var Container = React.createComponent({ componentDidMount: function () { var width = React.findDOMNode(this).offsetWidth; }, render: function () { <svg /> } }); 
+21
May 03 '15 at 9:20
source share

You can use the library I that I wrote, which controls your components displayed by size and passes them to you.

For example:

 import SizeMe from 'react-sizeme'; class MySVG extends Component { render() { // A size prop is passed into your component by my library. const { width, height } = this.props.size; return ( <svg width="100" height="100"> <circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" /> </svg> ); } } // Wrap your component export with my library. export default SizeMe()(MySVG); 



Demo: https://react-sizeme-example-esbefmsitg.now.sh/

Github: https://github.com/ctrlplusb/react-sizeme

It uses an optimized scroll / object algorithm, which I borrowed from people who are much smarter than me. :)

+5
Apr 14 '16 at 2:58
source share



All Articles