I am rendering a SVG ponent with labels. Those label ponents 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 ponent level, I need to
- render it for the first time
- retrieve the bounding box of the real SVG DOM node
- cache the bounding box for performance reason
- re-render the ponent to adjust the label position according to its cached bounding box
Then, at each redraw :
- render according to the cached bounding box
- pare label content between previous and updated props and if changed:
- update and cache the label bounding box
- re-render according to the updated and cached bounding box
So far, here is how I have implemented my label ponent:
var Label = React.createClass({
updateBBox: function() {
// Trigger re-rendering
this.setState({
bbox: this.getDOMNode().getBBox()
});
},
ponentDidMount: function() {
// Will trigger a re-rendering at mount
this.updateBBox();
},
ponentDidUpdate: 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-pliant way to implement this. Is using the state for caching this sort of slow 'putations' or DOM accesses allowed in the React way? Is double-rendering an unusual case for React?
I am rendering a SVG ponent with labels. Those label ponents 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 ponent level, I need to
- render it for the first time
- retrieve the bounding box of the real SVG DOM node
- cache the bounding box for performance reason
- re-render the ponent to adjust the label position according to its cached bounding box
Then, at each redraw :
- render according to the cached bounding box
- pare label content between previous and updated props and if changed:
- update and cache the label bounding box
- re-render according to the updated and cached bounding box
So far, here is how I have implemented my label ponent:
var Label = React.createClass({
updateBBox: function() {
// Trigger re-rendering
this.setState({
bbox: this.getDOMNode().getBBox()
});
},
ponentDidMount: function() {
// Will trigger a re-rendering at mount
this.updateBBox();
},
ponentDidUpdate: 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-pliant way to implement this. Is using the state for caching this sort of slow 'putations' or DOM accesses allowed in the React way? Is double-rendering an unusual case for React?
Share Improve this question edited Oct 7, 2014 at 11:10 gentooboontoo asked Oct 7, 2014 at 10:47 gentooboontoogentooboontoo 4,8533 gold badges22 silver badges15 bronze badges1 Answer
Reset to default 7My personal opinion is that you should put all state that matters to you into the state of a ponent. 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
}};
},
ponentDidMount: function() {
window.addEventListener("resize", this.updateDimensions);
this.updateDimensions();
},
ponentWillUnmount: function() {
window.removeEventListener("resize", this.updateDimensions);
},
ponentWillReceiveProps: 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.