9

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?

Alex
  • 978
  • 7
  • 23

2 Answers2

1

You don't need to set the shouldExport back to false, you could instead detect the change:

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

Then every toggle of the shouldExport would cause exactly one export. This however looks weird, I'd use a number that I'd increment:

componentWillReceiveProps(nextProps) {
    if (nextProps.exportCount > this.props.exportCount) {
        this.export();
    }
}
Dan Homola
  • 3,819
  • 1
  • 28
  • 43
1

I ran into the same problem in many occasions now, and since the React team doesn't encourage it i'll be using the props method for later development, but the problem is sometimes you want to return a value to the parent component, sometimes you need to check the child's state to decide whether to trigger an event or not, therefore refs method will always be my last haven, i suggest you do the same

Elias Ghali
  • 823
  • 1
  • 13
  • 29
  • Thanks. It's clear to me now that the answer is "it depends". It's not a black and white situation. – Alex Jul 25 '17 at 17:52