-1
    <button onClick = { () => this.restart()}>Restart</button>

Inside one of a ReactJs tutorial, that page shows a game and it defines the restart button just like above. I am not sure why it doesn't work if I replace it with the following.

<button onClick = {this.restart()}>Restart</button>

I feel like the above and the below are different only in the way that the above is a callback so that there will just be a small difference in the calling timing but it seems like there is more than that.

Can someone tell me why is that?

The whole code is at below.

class Game extends React.Component {

  constructor(props) {
    super(props)

    var cells = [];
    for (let i = 0; i < 6; i++) {
      cells.push(new Array(7).fill(0));
    }

    this.state = {
      player: false,
      cells: cells,
      winner: 0
    }
    this.handleClick = this.handleClick.bind(this)
  }
  checkDiagonal(row, col) {
    //find right and left tops
    var c = this.state.cells;
    var val = this.state.player ? 2 : 1;
    var rR = row;
    var cR = col;
    while (rR < 5 && cR < 6) {
      rR++;
      cR++;
    }

    while (rR >= 3 && cR >= 3) {
      if (c[rR][cR] == val && c[rR - 1][cR - 1] == val && c[rR - 2][cR - 2] == val && c[rR - 3][cR - 3] == val) {
        return 1
      }
      rR--
      cR--
    }

    var rL = row;
    var cL = col;

    while (rL < 5 && cL > 0) {
      rL++
      cL--
    }

    while (rL >= 3 && cL <= 3) {
      if (c[rL][cL] == val && c[rL - 1][cL + 1] == val && c[rL - 2][cL + 2] == val && c[rL - 3][cL + 3] == val) {
        return 1
      }
      rL--
      cL++
    }
    return 0
  }
  checkHorizontal(row, col) {
    var c = this.state.cells;
    var i = 6;
    var val = this.state.player ? 2 : 1;

    while (i >= 3) {
      if (c[row][i] == val && c[row][i - 1] == val && c[row][i - 2] == val && c[row][i - 3] == val) {
        return 1
      }
      i--
    }
    return 0
  }
  checkVertical(row, col) {
    var c = this.state.cells;
    var i = row;
    var val = this.state.player ? 2 : 1;

    if (i >= 3) {
      if (c[i][col] == val && c[i - 1][col] == val && c[i - 2][col] == val && c[i - 3][col] == val) {
        return 1
      }
    }
    return 0

  }
  checkVictory(row, col) {
    return this.checkVertical(row, col) || this.checkHorizontal(row, col) || this.checkDiagonal(row, col)


  }

  findAvailableRow(col) {


    for (var i = 0; i < 6; i++) {
      console.log(i, col)
      if (this.state.cells[i][col] == 0) {
        return i;
      }
    }
    return -1;
  }
  handleClick(row, col) {
    if (this.state.winner)
      return
    console.log("row: " + row + " | col: " + col)
    //console.log(this.state.cells)
    var temp = [];
    for (let i = 0; i < 6; i++) {
      temp.push(this.state.cells[i].slice())
    }
    var newRow = this.findAvailableRow(col)
    temp[newRow][col] = this.state.player ? 1 : 2
    this.setState({
      cells: temp,
      player: !this.state.player
    }, () => {


      if (this.checkVictory(newRow, col) > 0) {
        console.log("win")
        this.setState({
          winner: this.state.player ? 2 : 1
        })
      }


    })
  }
  restart() {
    var cells = [];
    for (let i = 0; i < 6; i++) {
      cells.push(new Array(7).fill(0));
    }
    this.setState({
      player: false,
      cells: cells,
      winner: 0
    })
  }
  render() {
    return ( <
      div >
      <
      h1 > {
        this.state.winner > 0 ? this.state.winner == 1 ? "Black Wins" : "Red Wins" : this.state.player ? "Blacks Turn" : "Reds Turn"
      } < /h1> <
      Board cells = {
        this.state.cells
      }
      handleClick = {
        this.handleClick
      }
      /> <
      button onClick = {
        () => this.restart()
      } > Restart < /button> <
      /div>
    )
  }
}

function Board(props) {
  var rows = []
  for (let i = 5; i >= 0; i--) {

    rows.push( < Row key = {
        i
      }
      row = {
        i
      }
      cells = {
        props.cells[i]
      }
      handleClick = {
        props.handleClick
      }
      />)
    }
    return ( <
      div > {
        rows
      } <
      /div>
    )
  }

  function Row(props) {
    var style = {
      display: "flex"
    }
    var cells = []
    for (let i = 0; i < 7; i++) {
      cells.push( < Cell key = {
          i
        }
        cell = {
          props.cells[i]
        }
        row = {
          props.row
        }
        col = {
          i
        }
        handleClick = {
          props.handleClick
        }
        />)
      }
      return ( <
        div style = {
          style
        } > {
          cells
        } <
        /div>
      )
    }

    function Cell(props) {

      var style = {
        height: 50,
        width: 50,
        border: "1px solid black",
        backgroundColor: "yellow"
      }

      return ( <
        div style = {
          style
        }
        onClick = {
          () => props.handleClick(props.row, props.col)
        } >
        <
        Circle cell = {
          props.cell
        }
        /> <
        /div>
      )
    }

    function Circle(props) {
      var color = "white"
      if (props.cell == 1) {
        color = "black"
      } else if (props.cell == 2) {
        color = "red"
      }

      var style = {
        backgroundColor: color,
        border: "1px solid black",
        borderRadius: "100%",
        paddingTop: "98%"
      }
      return ( <
        div style = {
          style
        } > < /div>
      )
    }

    ReactDOM.render( <
      Game / > ,
      document.getElementById('root')
    );
<div id="root"></div>
Eric
  • 389
  • 6
  • 16
  • 1
    Because doing so alters what `this` inside the method is. – Kevin B May 23 '18 at 20:21
  • Also `{this.restart()}` is invoking the function immediately and since there is no function returned will evaluate to `undefined` – charlietfl May 23 '18 at 20:28
  • What will this be? Originally it is pointing to the class itself so that we can retrieve restart() function from the class right? – Eric May 23 '18 at 20:29
  • It still is, but if you then corrected it to `{this.restart}` so that it isn't called immediately and is instead called on click, it would be the clicked element. – Kevin B May 23 '18 at 20:31
  • @charlietfl Can you tell me more about why it is called immediately so it is evaluated to undefined? – Eric May 23 '18 at 20:33

1 Answers1

2

Looks like you are facing a encapsulation problem.

The onClick listener takes a function as parameter so when you click the button that function is called. So when you write:

<button onClick = {() => this.restart()}>Restart</button>

You are giving the button a function that, when called, calls you function this.restart().

When you write like this:

<button onClick = {this.restart()}>Restart</button>

You are giving the button the return value of this.restart(). If that function doesn't return anything, this value will be undefined.

So let's say your restart function only logs something:

restart() {
  console.log("something");
}

In the first case, every time you click the button this function is executed and you can see the log. In the second case, that function is called when you render the component. So you would see that log only once, when the page renders, and if you click the button nothing would happen.

To achieve what you are trying to do, you should write like:

<button onClick = {this.restart}>Restart</button>

Because now you are not calling the function, you are just passing it to you onClick listener, and it will be called only when the button is clicked.

Thiago Loddi
  • 2,212
  • 4
  • 21
  • 35
  • 1
    Nice and plain explanation. I was writing something as a comment on the question but that answer nailed it. – devserkan May 23 '18 at 20:36