最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Maintaining Component Refs Through React.cloneElement - Stack Overflow

programmeradmin7浏览0评论

I have been testing the possible limitations/dangers of using React.cloneElement() to extend a ponent'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 ponents 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?

I have been testing the possible limitations/dangers of using React.cloneElement() to extend a ponent'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 ponents 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?

Share Improve this question edited Jun 20, 2020 at 9:12 CommunityBot 11 silver badge asked Jan 26, 2017 at 15:15 PrusprusPrusprus 8,0659 gold badges43 silver badges58 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 6

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);
      }
    }
  )
);
发布评论

评论列表(0)

  1. 暂无评论