Best practices for using React refs to call child function

Viewed 1361

I'm hoping for some clarity on the use of React refs for calling a child function. I have a Parent component that's a toolbar with a few buttons on it, and in the child component I have access to a library's export functionality. I'd like to call this export function on a button click in the parent component. Currently I'm using React refs to accomplish this:

Parent.js [ref]

class Parent extends React.Component {

  onExportClick = () => {
    this.childRef.export();
  }

  render() {
    return (
      <div>
        <button onClick={this.onExportClick} />Export</button>
        <Child ref={(node) => this.childRef = node;} />
      </div>
    )
  }
}

Child.js [ref]

class Child extends React.Component {

  export() {
    this.api.exportData();
  }

  render() {
    <ExternalLibComponent
      api={(api) => this.api = api}
    />
  }
}

This solution works fine, but I've seen a lot of disagreement on if this is the best practice. React's official doc on refs says that we should "avoid using refs for anything that can be done declaratively". In a discussion post for a similar question, Ben Alpert of the React Team says that "refs are designed for exactly this use case" but usually you should try to do it declaratively by passing a prop down.

Here's how I would do this declaratively without ref:

Parent.js [declarative]

class Parent extends React.Component {

  onExportClick = () => {
    // Set to trigger props change in child
    this.setState({
      shouldExport: true,
    });

    // Toggle back to false to ensure child doesn't keep 
    // calling export on subsequent props changes
    // ?? this doesn't seem right
    this.setState({
      shouldExport: false,
    });
  }

  render() {
    return (
      <div>
        <button onClick={this.onExportClick} />Export</button>
        <Child shouldExport={this.state.shouldExport}/>
      </div>
    )
  }
}

Child.js [declarative]

class Child extends React.Component {

  componentWillReceiveProps(nextProps) {
    if (nextProps.shouldExport) {
      this.export();
    }
  }

  export() {
    this.api.exportData();
  }

  render() {
    <ExternalLibComponent
      api={(api) => this.api = api}
    />
  }
}

Although refs are seen as an "escape hatch" for this problem, this declarative solution seems a little hacky, and not any better than using refs. Should I continue to use refs to solve this problem? Or should I go with the somewhat hacky declarative approach?

2 Answers
Related