gaearon/overreacted.io

Why does React hold to the value when clicked?

githubofwyh opened this issue · 2 comments

Article https://overreacted.io/how-are-function-components-different-from-classes/

Question

class ProfilePage extends React.Component {
 render() {
    const { user } = this.props;
    return (
      <button
        onClick={() => {
          setTimeout(() => {
            alert("Followed " + user);
          }, 3000);
        }}
      >
        Follow
      </button>
    );
  }
}

As we all know, closure is live linked rather than capturing the value.In this case, 'user' in setTimeout callback will linked with this code -- 'const { user } = this.props', but each render 'user' in 'const { user } = this.props' will change.So why is it possible to capture the value of a click?

TLDR: Closures are indeed live linked, but if a reference (here: user variable) leads to a primitive constant, this reference works as if it were just a capture of the value.

Full answer:

For the sake of simplicity let's imagine that the button is clicked right after it's rendered and this.props.user is changed right after that. Now, let's go through each render() step by step:

// this.props.user = 'Ruby'
render() {
    const { user } = this.props;
    // declare a constant variable user and assign 'Ruby' (primitive) to it (NOT the reference to this.props.user)
    return (
      <button
        onClick={() => {
          setTimeout(() => { 
            alert("Followed " + user);
          }, 3000);
  /* set a timeout, pass the unnamed arrow function, 
     store references to variables from the arrow function's closure, including our "user" primitive ("Ruby") */
        }}
      >
        Follow
      </button>
    );
  }
// this.props.user = 'Sapphire'
render() {
    const { user } = this.props;
    // declare a constant variable user and assign 'Sapphire' (primitive) to it (NOT the reference to this.props.user)
    return (
      <button
        onClick={() => {
          setTimeout(() => { 
            alert("Followed " + user);
          }, 3000);
  /* set a timeout, pass the unnamed arrow function, 
     store references to variables from the arrow function's closure, including our "user" primitive ("Sapphire") */
        }}
      >
        Follow
      </button>
    );
  }
// 1st setTimeout runs the callback
() => { 
  alert("Followed " + user); // user === user from the 1st closure === "Ruby"
}
// 2nd setTimeout runs the callback
() => { 
  alert("Followed " + user); // user === user from the 2nd closure === "Sapphire"
}

Note: If users were a let-variable, it would be possible to reassign it's value (changing this.props.users still won't work because primitives don't store any reference to sources of their values) after starting the 1st setTimeout and have the updated value in the output