Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practices for using React refs to call child function

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?

like image 588
Alex Avatar asked Jul 14 '17 14:07

Alex


People also ask

Why ref is not recommended in React?

We should not use ref attribute on function components because they do not have instances. React will assign the current property with Dom element when component mount and assign null to it when component unmount. ref updates happen before componentDidMount or componentDidUpdate methods.

When Should refs be used in React?

Refs are a function provided by React to access the DOM element and the React element that you might have created on your own. They are used in cases where we want to change the value of a child component, without making use of props and all.

How do you pass ref to child class component?

forwardRef is a function used to pass the ref to a child component. Let's take an example of a new library with an InputText component that will provide a lot of functionality, though, for now, we'll keep it simple: const InputText = (props) => ( <input {... props} /> ));


1 Answers

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();
    }
}
like image 143
Dan Homola Avatar answered Oct 06 '22 22:10

Dan Homola