1

I was reading this React related article. I have also looked at the discussion on closures and callbacks here and here and here.

In an example, given in the React related article, there are three components <App>, <Form>, <Button>. <App> passes a state value to <Form> component during initialization and also during componentDidMount lifecycle. <Form> passes a callback function to <Button> component along with the state value it received from <App>.

class App extends React.Component {
  state = { val: "one" }

  componentDidMount() {
    this.setState({ val: "two" })
  }

  render() {
    return <Form value={this.state.val} />
  }
}

const Form = props => (
  <Button
    onClick={() => {
      alert(props.value)
    }}
  />
)

When the button is clicked in the below <Button> component, it alerts a stale value "one" instead of the latest value "two".

class Button extends React.Component {
.....
  render() {
    return (
        <button onClick={this.props.onClick}>This one is stale</button>
    )
  }
}

The reason given in the React article for stale value is that the callback closes over props.value.

I do not understand how/why is the props.value being closed over in React component. The callback assignment in above is probably being done like this:

this.props.onClick = () => {
      alert(props.value)
    }

So why is the latest value not being picked up? How is the prop.value "closed over"?

The complete code is given here.

A Singh
  • 107
  • 7

1 Answers1

1

This is the complete code for the Button component:

class Button extends React.Component {
  shouldComponentUpdate() {
    return false;
  }

  render() {
    return (
      <div>
        <button onClick={this.props.onClick}>This one is stale</button>
        <button onClick={() => this.props.onClick()}>This one works</button>
      </div>
    );
  }
}

The Button component is set up to not rerender when passed new props, thus this.props.onClick will only be evaluated once on initial render and will always reference the first onClick function that was passed to it.

In contrast, even though it is also evaluated once, () => this.props.onClick() is set up to look up onClick on the component current props at the time the arrow function is actually executed. At that point, the props will have been updated with the new version of onClick.

IAmDranged
  • 2,890
  • 1
  • 12
  • 6
  • Are you saying that in the first case the `props.value` in `alert(props.value)` will be substituted with "one" - like `this.props.onClick = () => alert("one")` – A Singh May 10 '21 at 16:25
  • No. `Form` will render twice, each time with a different set of props and passing `Button` a different `onClick` function. Each `onClick` function closes over - ie keeps a reference to - the `props` passed to `Form` at the time of render - so `props.value` will always evaluate to `"one"` for the first `onClick` function, and `"two"` for the second. – IAmDranged May 10 '21 at 17:30
  • But ultimately, the reason why you're seeing the result that you see is because `Button` is set up to never rerender. – IAmDranged May 10 '21 at 17:31
  • What exactly does "closes over" mean, is it substitution of values ? – A Singh May 10 '21 at 17:51
  • It just means that a function captures and keeps a live reference to something - more specifically a variable that is in scope at declaration time. This is important because in javascript, functions can be executed in a different scope than the one in which they are declared. – IAmDranged May 10 '21 at 18:02