Search code examples
javascriptreactjsecmascript-6destructuring

Why doesn't const { innerRef, ...props } = this.props; grab a refrence to this.props.innerRef in react ref forwarding?


After reading forwarding refs from react legacy docs and following the examples from the SO post on using ref forwarding on classes, I tried to create a working example as following:

class Card extends React.Component {

  constructor(props) {
    super(props);
    this.myRef = React.createRef();
    //after three seconds focus myRef
    setTimeout(() => {
      console.log(this.myRef);
      this.myRef.current.focus();
    }, 3000);
  }

  render() {
    return <FancybtnWthRef ref={this.myRef} txt="random"/>;
  }
}

//Fancybtn class based componenet with ref forwarding

class Fancybtn extends React.Component {
  constructor(props) {
    super(props);
    const { innerRef, ...props2 } = this.props;
    console.log(innerRef, this.props.innerRef); //doesn't get live binding to this.props.innerRef
  }

  render() {
    return (
      <button className="fancy-btn" ref={this.innerRef} {...this.props2}>
        {this.props.txt}
      </button>
    );
  }
}

const FancybtnWthRef = React.forwardRef((props, ref) => {
  return <Fancybtn {...props} innerRef={ref} />;
});




ReactDOM.render(
  <Card />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>

On the other hand, if I grab the this.props.innerRef object directly for button, then it works:

class Card extends React.Component {

  constructor(props) {
    super(props);
    this.myRef = React.createRef();
    //after three seconds focus myRef
    setTimeout(() => {
      console.log(this.myRef);
      this.myRef.current.focus();
    }, 3000);
  }

  render() {
    return <FancybtnWthRef ref={this.myRef} txt="random"/>;
  }
}

//Fancybtn class based componenet with ref forwarding

class Fancybtn extends React.Component {
  constructor(props) {
    super(props);
    const { innerRef, ...props2 } = this.props;
    console.log(innerRef, this.props.innerRef);
  }

  render() {
    return (
      <button className="fancy-btn" ref={this.props.innerRef} {...this.props2}>
        {this.props.txt}
      </button>
    );
  }
}

const FancybtnWthRef = React.forwardRef((props, ref) => {
  return <Fancybtn {...props} innerRef={ref} />;
});





ReactDOM.render(
  <Card />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>

In plain javascript destructuring gets live binding of objects as following:

var obj = {x:1, obj2: {y: 33}}
const {x, obj2} = obj;
obj2.y = 44;
cconsole.log(obj.obj2.y); //gives 44

Using the same argument why doesn't const { innerRef, ...props } = this.props; grab the actual this.props.innerRef for the button?


Solution

  • const { innerRef, ...props2 } = this.props; does correctly get the innerRef property from props. However, this creates two local variables. It does not set the actual innerRef property on the instance itself. Thus, this.innerRef is undefined and passing that as the ref to the <button> has no effect. Note that unlike other languages like Java, accessing a variable without using this will not attempt to look up that variable in the fields of the enclosing class instance.

    You would have to manually assign the innerRef to the instance to see it work:

    this.innerRef = innerRef;
    

    Or, to destructure the properties onto this, you can rename the property like so:

    ({ innerRef: this.innerRef, ...this.props2 } = this.props);