React.js: Refs are not available on initial render
Asked Answered
K

2

14

I’m tying to position a circle in the middle of the component’s root DOM element:

var App = React.createClass({
    render: function() {
        return <svg ref="svg">
            <circle r="9" cx={this.centerX()} cy="15"/>
        </svg>;
    },
    centerX: function() {
        var svg = this.refs.svg.getDOMNode();
        return svg.offsetLeft + Math.round(svg.offsetWidth / 2);
    }
});

http://jsfiddle.net/NV/94tCQ/

Chicken-and-egg problem takes place here: this.refs is undefined on the first render. What’s the best way to solve this it? I would prefer not to reference external DOM nodes (such as document.body).

Kaczmarek answered 6/3, 2014 at 23:23 Comment(0)
D
23

It's not that refs isn't defined, it's that you're trying to access the DOM at the same time you are trying to generate it. this.refs.svg.getDOMNode will not return anything because the component has no real DOM representation in render.

To keep this more React-y, I would move cx to the component's state and update it after the element has been rendered to the DOM:

var App = React.createClass({
    componentDidMount: function() {
        var svg = this.refs.svg.getDOMNode();
        this.setState({
            cx: svg.offsetLeft + Math.round(svg.offsetWidth / 2)
        });
    },
    getInitialState: {
        return {
            cx: 0
        };
    },
    render: function() {
        return (
            <svg ref="svg">
                <circle r="9" cx={this.state.cx} cy="15" />
            </svg>
        );
    }
});
Digiovanni answered 7/3, 2014 at 0:53 Comment(3)
Of course, if you know the dimensions of the svg element you could also calculate the proper center within render to avoid having to read from the DOM. It won't be particularly pronounced in this case, but for performance it's best to avoid reading from the DOM and rerendering if possible.Fanfaron
@BenAlpert Good point. Although in this case the code is referencing offsetLeft, which is relative to the DOM node's nearest positioned parent. It's not possible to get that number until both the offsetParent and this element have been rendered to the DOM. This might be better solved in CSS, but this seems most appropriate given the code in the original post.Digiovanni
I agree completely. My comment was mostly addressed to the original poster as an optional enhancement.Fanfaron
P
2

A hacky way to solve this issue is to return a dummy value when it is not inserted in the DOM yet and when it is (using componentDidMount) then re-draw the element.

    centerX: function() {
        if (!this.refs || !this.refs.svg) {
            return 0;
        }
        var svg = this.refs.svg.getDOMNode();
        return svg.offsetLeft + Math.round(svg.offsetWidth / 2);
    },
    componentDidMount: function() {
        this.forceUpdate();
    }

http://jsfiddle.net/vjeux/94tCQ/4/

Pisci answered 7/3, 2014 at 0:41 Comment(1)
That looks hacky indeed. :)Haddad

© 2022 - 2024 — McMap. All rights reserved.