I am rendering a SVG component with labels. Those label components need to be correctly laid out depending on their text content (and so their size) in order to avoid overlapping themselves.
To get the real size of each labels, it seems a double-render is needed each time a label content is updated.
At the label component level, I need to
Then, at each redraw :
So far, here is how I have implemented my label component:
var Label = React.createClass({
updateBBox: function() {
// Trigger re-rendering
this.setState({
bbox: this.getDOMNode().getBBox()
});
},
componentDidMount: function() {
// Will trigger a re-rendering at mount
this.updateBBox();
},
componentDidUpdate: function(prevProps, prevState) {
// If content has changed, re-render
if (this.props.content !== prevProps.content) {
this.updateBBox();
}
},
render: function() {
// Render according to size from current bounding box if any cached
// ...
}
});
So, my question is not about the algorithm but if there is a better React-compliant way to implement this. Is using the state for caching this sort of slow 'computations' or DOM accesses allowed in the React way? Is double-rendering an unusual case for React?
My personal opinion is that you should put all state that matters to you into the state of a component. So yes, the way you propose is the right way to do it.
As far as I know, double renders are the only way to get the size of a block as state, since the block needs to be drawn first. Since React is so performant, this is usually no problem. if it is, you use PureRenderMixin or other checks on shouldComponentUpdate within the children to ensure performance.
Note however that it might be smart to write this as a mixin, because of "don't repeat yourself".
I do responsive SVG stuff quite frequently so I often need x, y width and height from a block element (svg or html).
I've written this little mixin called BoundingRectAware:
var shallowEqual = require('react/lib/shallowEqual');
// keep width and height of an element. You must make a "boundingRectTarget" ref
// to the element you would like to track
module.exports = {
getInitialState: function() {
return {rect: {
left: null, top: null, right: null, bottom: null, width: null, height: null
}};
},
componentDidMount: function() {
window.addEventListener("resize", this.updateDimensions);
this.updateDimensions();
},
componentWillUnmount: function() {
window.removeEventListener("resize", this.updateDimensions);
},
componentWillReceiveProps: function() {
this.updateDimensions();
},
updateDimensions: function() {
if (this.refs.boundingRectTarget) {
var rect = this.refs.boundingRectTarget.getDOMNode().getBoundingClientRect();
if (!shallowEqual(this.state.rect, rect)) {
this.setState({rect: rect});
}
}
}
};
this can be used like so:
var FooComponent = React.createClass({
mixins: [BoundingRectAware],
render: function() {
return <div className="foo-class" ref="boundingRectTarget"/>;
}
});
within FooComponent, you will automatically get the boundingClientRect as state. I would implement something alike if you use getBBox() in more than one place.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With