0

Can someone briefly explain why this keyword is undefined in a function expression, but defined in an arrow function. I'm simply trying to follow the tutorial on the React website and they gloss over a seemingly important concept regarding confusing behavior of this but the link refers to a very in-depth article which is not at all friendly to beginners. After completing the tutorial I wanted to play around and try to understand what works and what doesn't and I tried replacing an arrow function with a function expression, and suddenly the onClick event failed to work.

This is the code as written in the tutorial:

class Board extends React.Component {
    renderSquare(i) {
        return (
            <Square
                value={this.props.squares[i]}
                onClick={() => {
                    this.props.onClick(i);
                }} />
        );
    }
//other stuff
}

And I wanted to change the arrow function to a traditional function expression, but had some problems

class Board extends React.Component {
    renderSquare(i) {
        return (
            <Square
                value={this.props.squares[i]}
                onClick={function() {
                    console.log(this);          //undefined
                    console.log(this.props);    //throws exception
                    this.props.onClick(i);      //throws exception
                }} />
        );
    }
}

Why is this undefined in the function expression? When I read the documentation for arrow function and function expressions on MDN it seems that the opposite should be true. What's going on here?

Here the codepen Here's the full project:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

function Square(props) {
    return (
        <button className="square" onClick={props.onClick}>
            {props.value}
        </button>
    );
}

class Board extends React.Component {
    renderSquare(i) {
        return (
            <Square
                value={this.props.squares[i]}
                onClick={() => this.props.onClick(i) } />
        );
    }

    render() {
        return (
            <div>
                <div className="board-row">
                    {this.renderSquare(0)}
                    {this.renderSquare(1)}
                    {this.renderSquare(2)}
                </div>
                <div className="board-row">
                    {this.renderSquare(3)}
                    {this.renderSquare(4)}
                    {this.renderSquare(5)}
                </div>
                <div className="board-row">
                    {this.renderSquare(6)}
                    {this.renderSquare(7)}
                    {this.renderSquare(8)}
                </div>
            </div>
        );
    }
}

class Game extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            isX: true,
            history: [{
                squares: Array(9).fill(null)
            }],
            stepNumber: 0
        }
    }

    handleClick(i) {
        const history = this.state.history.slice(0, this.state.stepNumber + 1);
        const current = this.state.history[history.length - 1];
        const squares = current.squares.slice();
        if (squares[i] == null) {
            squares[i] = this.state.isX ? 'X' : 'O';
        } else {
            console.log("Square [" + i + "] is already marked!");
            return;
        }
        this.setState({
            history: history.concat([{squares: squares}]),
            isX: !this.state.isX,
            stepNumber: history.length
        });
    }

    jumpTo(step) {
        this.setState({
            stepNumber: step,
            xIsNext: (step % 2) === 0,
        });
    }

    render() {
        const history = this.state.history;
        const current = this.state.history[this.state.stepNumber];
        const winner = calculateWinner(current.squares);

        const moves = history.map((step, move) => {
            const desc = move ?
                'Go to move #' + move :
                'Go to game start';
            return (
                <li key={move}>
                    <button onClick={() => this.jumpTo(move)}>{desc}</button>
                </li>
            );
        });

        let status;
        if (winner) {
            status = 'Winner: ' + winner;
        } else {
            status = 'Next player: ' + (this.state.isX ? 'X' : 'O');
        }
        return (
            <div className="game">
                <div className="game-board">
                    <Board
                        squares={current.squares}
                        onClick={(i) => this.handleClick(i)}/>
                </div>
                <div className="game-info">
                    <div>{status}</div>
                    <ol>{moves}</ol>
                </div>
            </div>
        );
    }
}

function calculateWinner(squares) {
    const lines = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6],
    ];
    for (let i = 0; i < lines.length; i++) {
        const [a, b, c] = lines[i];
        if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
            return squares[a];
        }
    }
    return null;
}

// ========================================

ReactDOM.render(
    <Game />,
    document.getElementById('root')
);
moonlightcheese
  • 10,664
  • 9
  • 49
  • 75
  • Does this help? [How to access the correct `this` inside a callback](https://stackoverflow.com/q/20279484/218196). Essentially in a function expression/declaration, the `this` value depends on how the function is called. And in the case of event handlers, they are called in such a way that `this` **will not** refer to your React component. Arrow functions solve this because inside them `this` is resolved lexically like any other variable (and `this` inside `renderSquare` refers to your React component. – Felix Kling Feb 18 '22 at 11:59
  • Of course there is also [How does the "this" keyword work?](https://stackoverflow.com/q/3127429/218196) and [Are 'Arrow Functions' and 'Functions' equivalent / interchangeable?](https://stackoverflow.com/q/34361379/218196) – Felix Kling Feb 18 '22 at 12:03
  • My comment won't answer your question directly, but FWIW the tutorials regarding "this" is in-depth because the "context of execution" and "this" is a deep subject, yet fundamental and essential. It's better you have a solid understanding of them. Having said that, in React the class based approach is pretty old, and function based approach is taking over. So in terms of React, you might as well just skip learning the class based approach. – dungarian Feb 18 '22 at 12:04

0 Answers0