1

Reading through React docs I came across the following piece of code:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

My question is this:

In componentDidMount() the arrow function () => this.tick() "captures" the enclosing this value and thus the proper tick method is called on the <Clock> component.

However tick uses this on its own to subsequently call the setState method. How is this working without any bindings? In other words, how is this.setState working?

Thanks

stratis
  • 7,750
  • 13
  • 53
  • 94
  • `()=>` actually binds current this to function. Its similar to `function(){}.bind(this)`. You can also try `setTimeout(this.tick.bind(this), 1000)` – Rajesh Apr 04 '17 at 05:48
  • @Rajesh What you are suggesting only applies to `this.tick`. Inside the `tick` method however `this` **is used again** to call `setState`. That's the `this` that troubles me the most. – stratis Apr 04 '17 at 06:01
  • What do you think would `this` hold in `tick`? – Rajesh Apr 04 '17 at 06:02
  • @Rajesh I believe that `this` within the `tick` function should be undefined or the global Object/window. Not a react component though... – stratis Apr 04 '17 at 06:07
  • 1
    For clarity on `this`, refer: http://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work. A simple explanation, a function's this points to callee(*instance associated with it*) or default(*window or undefined*). Now when you do `()=>`, you are binding `this` of `componentDidMount` to `tick`, which points to `component. Hence setState works fine – Rajesh Apr 04 '17 at 06:20
  • hope it solves your query. If not, feel free to ask. :-) – Rajesh Apr 04 '17 at 06:31

2 Answers2

1

According to the bind function documentation, Function.prototype.bind will create a new function with the same body and scope as the original function, but where this in the scope of the new function refers to the argument that is passed into bind. This means you can pass a variable, lets say newContext into the bind function like: tick.bind(newContext), and every instance of this inside of tick will be evaluated as newContext. Although this is a keyword referring to the context of the scope, we have changed the scope's context and so this has a different value.

The arrow notation does something similar, and will replace the this inside the value tick with the value of the context of the arrow function's scope. This is why the this keyword inside of tick does not have an undefined value.

rageandqq
  • 2,221
  • 18
  • 24
1

I think Rajesh has already explained it in the comments, still I will give it a try to explain.

An arrow function does not create its own this context, so this has its original meaning from the enclosing context i.e from where it is being called which is componentDidMount in your case.

  componentDidMount() {
    // this refers to React.Component instance
    this.timerID = setInterval(
      () => this.tick(),  // <----------------Same this is available here
      1000
    );
  }

If you use bind you can achieve similar things via setting context via bind.

  componentDidMount() {
    // this refers to React.Component instance from inside cdm method
    this.timerID = setInterval(
      this.tick.bind(this),  // <----------------Same this is available here
     // We have set it react component instance via bind
      1000
    );
  }

Now after all those just look only at this method declaration -

  tick() {
    this.setState({
      date: new Date()
    });
  }

Upto this point we can not say exactly what will be the value of this inside tick. We also can not say that 'setState' will be available on this or not.

Unless and until we see from where tick will get called and what execution context will be attached to tick at run time.

Now if we see from where tick is getting called - componentDidMount. componentDidMount is react component's life cycle method and it ( associated context i.e this) definitely has setState method available.

And in componentDidMount we had set this to react-component context either via arrow function () => {} or via bind. This is the reason because of which setState is avaialble in tick when called and you don't see any error.

But if you don't use arrow function or explicit bind you will be receiving following error on each time when setInterval calls tick function there by associating a global this with tick.

  "message": "Uncaught TypeError: this.setState is not a function",

See the below code snippet via running to see the error

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    // now this inside tick will be pointing to setIntervals execution context which could be global
    this.timerID = setInterval(
      this.tick,
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root">
</div>
WitVault
  • 23,445
  • 19
  • 103
  • 133