What's the proper way of passing a ref to a prop?

In general the "ref" feature is an anti-pattern in React. It exists to enable side-effect driven development, however in order to benefit the most from the React way of programming you should try to avoid "refs" if possible.

As for your particular issue, passing a child a ref to it's sibling is a chicken vs. egg scenario. The ref callback is fired when the child is mounted, not during render which is why your example doesn't work. One thing you can try is pushing the ref into state and then reading from state into the other child. So:

<One ref={c => !this.state.one && this.setState({ one: c })}/>
<Two one={this.state.one}/>

Note: without the !this.state.one this will cause an infinite loop.

Here is a codepen example of this working (look at the console to see the sibling ref logged): http://codepen.io/anon/pen/pbqvRA


This is now much simpler using the new ref api (available since React 16 - thanks to perilandmishap for pointing that out).

class MyComponent extends React.Component {
  constructor (props) {
    super(props);
    this.oneRef = React.createRef();
  }

  render () {
    return (
      <React.Fragment>
        <One ref={this.oneRef} />
        <Two one={this.oneRef} />
      </React.Fragment>
    }
  }
}

You would consume the prop in Two like:

this.props.one.current

A few things of note with this approach:

The ref will be an object with a current property. That property will be null until the element/component is mounted. Once it's mounted, it will be the instance of One. It should be safe to reference it once <Two /> is mounted.

Once the <One /> instance is unmounted, the current property on the ref returns to being null.


In general, if you need to pass a reference to something that may not be set at call time, you can pass a lambda instead:

<One ref={c => this.one = c}/>
<Two one={() => this.one}/>

and then reference it as

this.props.one()

If it has been set when you call it, you'll get a value. Before that, you'll get undefined (assuming it hasn't otherwise been initialized).

It bears noting that you won't necessarily re-render when it becomes available, and I would expect it to be undefined on the first render. This is something that using state to hold your reference does handle, but you won't get more than one re-render.

Given all that, I would recommend moving whatever code was using the ref to One in Two up into the component that is rendering One and Two, to avoid all the issues with both this strategy, and the one in @Carl Sverre's answer.