Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React.js: Refs are not available on initial render

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).

like image 807
NVI Avatar asked Mar 06 '14 23:03

NVI


People also ask

How do you create refs in React?

You can create a ref by calling React. createRef() and attaching a React element to it using the ref attribute on the element. We can “refer” to the node of the ref created in the render method with access to the current attribute of the ref. From the example above, that would be this.

Why are string refs legacy React?

String refs force React to keep track of currently executing component. This is problematic because it makes react module stateful, and thus causes weird errors when react module is duplicated in the bundle.

Why are refs deprecated?

We advise against it because string refs have some issues, are considered legacy, and are likely to be removed in one of the future releases. If you're currently using this. refs. textInput to access refs, we recommend the callback pattern instead.


2 Answers

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>
        );
    }
});
like image 169
Ross Allen Avatar answered Oct 15 '22 00:10

Ross Allen


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/

like image 44
Vjeux Avatar answered Oct 14 '22 23:10

Vjeux