0

I have followed React JS Crash Course - 2019 on youtube and I was not able to find the answer to the following question.

Why the method context in parent component is not overwritten by the child bind call.

The codebase can be found here https://github.com/bradtraversy/react_crash_todo

But to simplify the question here is a small code snippet.

Parent

class App extends Component {
  state = {
    todos: []
  }


  // Toggle Complete
  markComplete = (id) => {
    this.setState({ todos: this.state.todos.map(todo => {
      if(todo.id === id) {
        todo.completed = !todo.completed
      }
      return todo;
    }) });
  }

  render() {
    return (
        <TodoItem markComplete={this.markComplete}  />
    );
  }
}

Child

export class TodoItem extends Component {

  render() {
    const { id, title } = this.props.todo;
    return (
        <input type="checkbox" onChange={this.props.markComplete.bind(this, id)} /> {' '}
    )
  }
}

From my understanding of bind https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind I would expect that when the mark markComplete is called that the "this" would be the child context but it stays the parent.

  markComplete = (id) => {
    this.setState({ todos: this.state.todos.map(todo => {
    ^^^^ why it is not child
      if(todo.id === id) {
        todo.completed = !todo.completed
      }
      return todo;
    }) });
  }

I do understand the public class fields syntax.

Is react doing some magic? https://github.com/facebook/react/blob/64e1921aab4f7ad7e2f345f392033ffe724e52e9/packages/events/EventPluginHub.js#L148

From answer @Li357 https://stackoverflow.com/a/51759791/4322180

bronhy
  • 31
  • 5

4 Answers4

1

That's because you are using an arrow function. Arrow Functions use a "Lexical this" which is not the "caller". Try to write your method like this, then the bind should work :

  markComplete(id) {
    this.setState({ todos: this.state.todos.map(todo => {
      if(todo.id === id) {
        todo.completed = !todo.completed
      }
      return todo;
    }) });
  }

Lexical this : http://es6-features.org/#Lexicalthis

[Edit]

You could also write your function as a "classical" function with function : markComplete = function(id) { /*...*/ }

  • I understand lexical "this" but isn't that also overwritten by the bind call? – bronhy Jun 07 '19 at 13:30
  • 1
    No, that's the prupose of the lexical this, it "captures" the `this` so that it is never changed thereafter – Julien Barrois Jun 07 '19 at 13:31
  • `const func = () => { console.log(this) }` is equivalent to `const that = this; const func = function() { console.log(that) }`. Written like that, we see that there is no usage of `this` anymore in the function, so binding it does not change anything – Julien Barrois Jun 07 '19 at 13:33
  • Ok in that case it does make sense. But following question would then be. Does react wrap "{this.props.markComplete.bind(this, id)}" in a new method before passing it to real event handler? And that is the reason why bind is needed so he knows which id to pass? – bronhy Jun 07 '19 at 13:43
  • `bind` takes only one parameter AFAIK, so `this.props.markComplete.bind(this, id)` is the same thing as `this.props.markComplete.bind(this)`. I guess what you meant is `onChange={() => this.props.markComplete(id)}` [Edit] `bind` actually takes more than one argument, but it is usually used to change the `this`. – Julien Barrois Jun 07 '19 at 13:47
  • So your code is actually correct regarding this. And if your question was "does `bind` returns a new method?", yes it does. – Julien Barrois Jun 07 '19 at 13:54
  • React says it the same https://reactjs.org/docs/handling-events.html#passing-arguments-to-event-handlers. Bind does produce a new function with new this. And that is why I was confused. New function this is not the same as an executed function this. I just don't jet get the idea of the code construct its like a function in a function? – bronhy Jun 07 '19 at 13:57
  • yes, and with an arrow function, changing `this` does not change anything actually :). – Julien Barrois Jun 07 '19 at 13:59
1

Since MDN does not mention this in its documentation here is the official link to the ECMASript which explains this case.

https://www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.bind

Pay attention to -> NOTE 2

If Target is an arrow function or a bound function then the thisArg passed to this method will not be used by subsequent calls to F.

bronhy
  • 31
  • 5
0

Arrow functions automatically bind to the current context, which would be the parent in your case.

Change it to a normal function, and bind manually in the parent constructor

andy mccullough
  • 9,070
  • 6
  • 32
  • 55
0

You don't need to use bind into your child component.

Why it is not child?

Because when you use arrow function, this context refers to the class itself. If you want to refer this as a child context when you bind your function into your child component, don't use arrow function.

 markComplete (id) {
    this.setState({ todos: this.state.todos.map(todo => {
      if(todo.id === id) {
        todo.completed = !todo.completed
      }
      return todo;
    }) });
}
Radonirina Maminiaina
  • 6,958
  • 4
  • 33
  • 60
  • I am more interested into why as to how. – bronhy Jun 07 '19 at 13:31
  • It's due to lexical scoping - the context of `this` in an arrow function references the class (so you don't have to bind functions to the class since they are accessible inside the lexical scope/arrow function) - if you did not use an arrow function you would have to bind the function to the class in the constructor.. [Good explanation of lexical scoping in React](https://stackoverflow.com/a/37198880/10431732) - [Example of binding a function in the constructor](https://codepen.io/gaearon/pen/xEmzGg?editors=0010) – Matt Oestreich Jun 07 '19 at 13:37
  • Updating my answer as OP asked. – Radonirina Maminiaina Jun 07 '19 at 13:40