Reputation: 116
I have been working through the React tutorial at https://reactjs.org/tutorial/tutorial.html and I am getting through it well enough. I have one thing that I haven't been able to wrap my head around though. Here is the code in full (codepen: https://codepen.io/gaearon/pen/EmmOqJ?editors=0010):
function Square(props) {
return (
<button className="square"
onClick={props.onClick}>
{props.value}
</button>
);
}
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;
}
class Board extends React.Component {
renderSquare(i) {
return (
<Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)}/>
);
}
render() {
return (
<div>
<div className="status">{status}</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 = {
history: [{
squares: Array(9).fill(null),
}],
xIsNext: true,
};
}
handleClick(i) {
const history = this.state.history;
const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat([{
squares: squares,
}]),
xIsNext: !this.state.xIsNext,
});
}
render() {
const history = this.state.history;
const current = history[history.length - 1];
const winner = calculateWinner(current.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? '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>{/* TODO */}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
In the tutorial they pass a callback function handleClick(i)
from the parent Game
component to the child Board
component and from there to the child Square
component. My question is on how is the argument i
set? My guess is that the starting point is when renderSquare(i)
is called in the Board
component. From there I am lost as to how i
it makes its way to handleClick(i)
. Is it stored in the 'onClickfunction object passed to
Squarefrom
Board`?
Upvotes: 3
Views: 142
Reputation: 669
You are correct in your understanding of the flow and origin of the onClick
function, it is defined in the higher level component Game.
The Game
component defines the method onClick
that can be invoked to handle something.
In reverse order,
The HTML component [button
] gives a promise to say, if you give me a
callback function that matches a said signature, I will call it
whenever something happens (clicked in this event).
Now a higher component (parent to button) to the button Square
function, is both interested in
But because the Square function itself has no real use case for this event, it decided to delegate the handling of that event to its parent same way as the button did.
But it goes a step further to say, if you are interested in receiving this notification, my contract is further extended to say, I will only allow notification of the event if and only if you pass in your callback through a member of my input object as onClick
.
So another higher level component Board
(parent to Square) will
somehow need to satisfy component Square
requirements of passing in an object
with a member onClick
. Notice some things though, the renderSquare
method member of Board
,
onClick
properties when initiating a new instance of Square
, and subsequently returning that instance (because JS supports Higher Order Functions Higher Order Functions)It defines an anonymous function
as a delegate, of which it's implementation is just to return a member of the props called onClick
(it will appear that the Board also had a contract with any potential parent that there must be a member onClick that's passed on to it)...please note that since this syntax is not yet officially adopted, babel is used to transpile these code into something that a typical browser will understand.
renderSquare
method and or Board itself is not doing anything to the onClick member that's been passed around except just returning it in its own anonymous function on onClick={() => this.props.onClick(i)}
I assume you know how the result of that statement will look, but for argument's sake it basically becomes
function onClick(){return [this].props.onClick(i);}
Game
(Board
parent), is the one that defines and provides the actual implementation that satisfies the requirements of the onClick
(as defined by button component) method and passes it on as props
to the child component Board
. Now it means whenever Board
has an encounter with the onClick
specifically from its props
, it will essentially be accessing the anonymous function defined in Game
which subsequently returns Game
's handleClick
function see point 3.2 above. To attempt to answer your question
My question is on how is the argument i set? My guess is that the starting point is when renderSquare(i) is called in the Board component. From there I am lost as to how i it makes its way to handleClick(i). Is it stored in the 'onClick function object passed toSquarefromBoard`?
The most important thing to notice here is that, all of this code higher up is all blueprint or definitions on how the code will work when active or invoked, think of it a just a pipeline. So the definition does not mean that the handleClick(i)
is being called right away, infact it is not called up until later when our button component fires the event, when something causes / satisfied button to publish the event, of which when it happens, the reverse of these steps I've just tried to explain happens all the way down until the root Game's handleClick
function gets invoked.
I realize that my answer could be very long but I hope it paints a message and hopefully helps you to sort of visualize the flow of things.
Upvotes: 2
Reputation: 613
Your guessings were good ! I tried to comment the key aspects of the code on this gist : https://gist.github.com/Sangrene/d6784a855e0e3324e5b499e3a5242c45
Upvotes: 2
Reputation: 36905
My guess is that the starting point is when renderSquare(i) is called in the Board component. From there I am lost as to how i it makes its way to handleClick(i)
You are on the right track.
Within Board.render()
, this.renderSquare(...)
is called with a number 1~8
.
And renderSquare
has an onClick
handler, this.props.onClick(i)
.
renderSquare(i) {
return (
<Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)}/>
);
}
this.props
is passed from the parent, Game.render()
.
So this.props.onClick
is from <Board onClick={...} />
class Game extends React.Component {
handleClick(i) {
...
}
render() {
...
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => this.handleClick(i)}
/>
</div>
...
</div>
);
}
}
So this.props.onClick
matches the <Board onClick={...} />
where it's implemented as (i) => this.handleClick(i)
, in which this.handleClick(i)
refers to Game.handleClick(i)
.
Upvotes: 3