简体   繁体   中英

Maintaining Component Refs Through React.cloneElement

I have been testing the possible limitations/dangers of using React.cloneElement() to extend a component's children . One possible danger I've identified is the possible overwriting of props such as ref and key .

However, as per React's 0.13 release candidate (back in 2015):

However, unlike JSX and cloneWithProps, it also preserves refs. This means that if you get a child with a ref on it, you won't accidentally steal it from your ancestor. You will get the same ref attached to your new element.

[...]

Note: React.cloneElement(child, { ref: 'newRef' }) DOES override the ref so it is still not possible for two parents to have a ref to the same child, unless you use callback-refs.

I have written a small React application that clones children components pushed through, testing for the validity of refs at two levels:

class ChildComponent extends React.Component{
  constructor(props){
    super(props);   

    this.onClick = this.onClick.bind(this);
    this.extendsChildren = this.extendChildren(this);
  }

  onClick(e) {
    e.preventDefault();

    try{
      alert(this._input.value);
    }catch(e){
      alert('ref broken :(');
    }
  }

  extendChildren(){
    return React.Children.map(this.props.children, child => {
      return React.cloneElement(
        child,
        {
          ref: ref => this._input = ref
        }
      );
    });
  }

  render() {
    return(
      <div>
      <button onClick={this.onClick}>
        ChildComponent ref check
      </button>
      {this.extendChildren()}
    </div>
    );
  }
}


class AncestorComponent extends React.Component{
  constructor(props){
    super(props);

    this.onClick = this.onClick.bind(this);
  }

  onClick(e) {
    e.preventDefault();

    try{
      alert(this._input.value);
    }catch(e){
      alert('ref broken :(');
    }

  }

  render() {
    return (
    <div>
        <p>
          The expected behaviour is that I should be able to click on both Application and ChildComponent check buttons and have a reference to the input (poping an alert with the input's value).
        </p>
      <button onClick={this.onClick}>
        Ancestor ref check
      </button>
      <ChildComponent>
        <input ref={ref => this._input = ref} defaultValue="Hello World"/>
      </ChildComponent>
    </div>
    );
  }
}

However, cloningElements inside my ChildComponent overwrites the AncestorComponent's ref prop from the input field, where I would expect that ref prop to be preserved, alongside the new ref I defined as part of the React.cloneElement .

You can test this by running the CodePen .

Is there anything I'm doing wrong, or has this feature been dropped since?

As per Dan Abramov's response , overwriting the reference, even with a callback, is still going to overwrite the reference. You'll need to call the current reference as part of the callback declaration:

return React.Children.map(this.props.children, child =>
  React.cloneElement(child, {
    ref(node) {
      // Keep your own reference
      this._input = node;
      // Call the original ref, if any
      const {ref} = child;
      if (typeof ref === 'function') {
        ref(node);
      }
    }
  )
);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM