Search code examples
javascriptreactjsclassthisarrow-functions

this keyword behavior in classes and react component ( Arrow function vs regular function)


I'm trying to understand the behavior of the this keyword in a react component ( Arrow function vs Regular function) when paired with a event handler.

For this, I created two examples, one with HTML/vanilla JS, the other one with React.

HTML/VanillaJS

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button class="btn-arr">Log this Arr</button>
    <button class="btn-reg">Log this Reg</button>

    <script>

        class App {
            logThisArr = () => {
                console.log('Arr : ', this);
            };
            logThisReg = function () {
                console.log('Reg : ', this);
            };
        }

        const app = new App();

        const btnArr = document.querySelector('.btn-arr');
        const btnReg = document.querySelector('.btn-reg');

        btnArr.addEventListener('click', app.logThisArr); // Arr : App {logThisArr: ƒ, logThisReg: ƒ}

        btnReg.addEventListener('click', app.logThisReg); // Reg : <button class="btn-reg">Log this Reg</button>

    </script>
</body>

</html>

React

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import registerServiceWorker from './registerServiceWorker';

class App extends Component {
    logThisArr = () => {
        console.log('arrow : ', this);
    };
    logThisReg = function() {
        console.log('Reg : ', this);
    };

    render() {
        return (
            <div className="App">
                <button onClick={this.logThisArr}>Log this Arr</button>
                {/* Arr : App {props: {…}, context: {…}, refs: {…}, updater: {…}, logThisArr: ƒ, …}*/}

                <button onClick={this.logThisReg}>Log this Reg</button>
                {/*Reg : undefined */}
            </div>
        );
    }
}

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

Why don't I get the same output when using a regular function ? In react I get "undefined" and in vanillaJS I get the button object.

Thank you !


Solution

  • Alright, so, thank you both.

    I think I understand a bit better after a lot of reading.

    It goes way deeper than the original post. We first need to understand the difference between "execution context" and "lexical scope". http://ryanmorr.com/understanding-scope-and-context-in-javascript/

    Then we can start looking at the behavior of "this" in regular and arrow functions.

    Inside regular fonctions :

    • The "this" value changes depending on the "execution context", in other words, the "this" is a reference to who or what called it (usually an object, I.E. a button object or a class instantiated object).
    • If "nothing" called it(especially the case with callbacks), then, the "this" looses its reference to the initial object that called it and is set to a default value ( that default value is dependent on whether we are in node, or a browser, or in strict mode or using a library(not sure for the library but I think?) )
    • The "this" value can be explicitly specified with "call()","apply()" and "bind()".

    Inside arrow functions :

    • The arrow function doesn't have its own "execution context"
    • The arrow function binds the context statically on the declaration, in other words, the value of "this" is "captured" PERMANENTLY and it ALMOST ALWAYS refers to the value of "this" of the "parent/enclosing lexical scope" of the function (=the parent context).
    • The "this" value can never be rebinded. But the other parameters inside the arrow function can them be rebinded with the "bind()" keyword.
    • In the special case of defining methods with arrow functions in objects literal, the "this" refers to the window object : https://dmitripavlutin.com/when-not-to-use-arrow-functions-in-javascript/

    That's why in react the use of arrow functions or the use of ".bind()" is necessary, because we cannot rely on regular fonctions ( in which the "this" keeps changing) to access the attributes of our instantiated object of App class.

    In our specific case, as of why do I get "undefined" when using the regular fonction in the react code, TBT I'm not exactly sure (maybe because of react internal shenanigans) but I can guess it's because of two things :

    • "this" lost its "execution context"(in other terms it lost its reference to the object that called it) so it becomes the default ( either "undefined", when in "strict mode", or the "window object" when not in strict mode and in a browser)
    • In ES6 classes, the default implicit setting is set to "strict mode", thus we get "undefined".

    Anyways, this is not exhaustive, and I havent understood everything yet, but I now understand a bit better why we need to bind/use arrow fonctions in React and how they work.

    These really helped me get to the bottom of things :

    And also the link mentionned by @brian Thomoson : How to access the correct `this` inside a callback?

    Thank you guys.

    Regards,