What Is React?
React is the most popular front-end JavaScript library in the field of web development. It is used by large, established companies and newly-minted startups like Netflix, Airbnb, Instagram, and the New York Times plus many more. React has many more advantages, making it a better choice.
Components
React has a few different kinds of components, but we’ll start with React.Component
subclasses:
class SocialList extends React.Component {
render() {
return (
<div className="social-list">
<h1>Social List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Facebook</li>
</ul>
</div>
);
}
}
Here, SocialList is a React component class, or React component type. A component takes in parameters, called props
(short for “properties”), and returns a hierarchy of views to display via the render
method.
The render
method returns a description of what you want to see on the screen. React takes the description and displays the result. In particular, render
returns a React element, which is a lightweight description of what to render. The <div />
syntax is transformed at build time to React.createElement('div')
. The example above is equivalent to:
React.createElement("div", {
className: "social-list"
},
React.createElement("h1", null, "Social List for ", props.name),
React.createElement("ul", null, /*#__PURE__*/React.createElement("li", null, "Instagram"),
React.createElement("li", null, "WhatsApp"),
React.createElement("li", null, “Facebook")))
The SocialList
component above only renders built-in DOM components like <div />
and <li />
. But you can compose and render custom React components too. For example, we can now refer to the whole social list by writing <SocialList />
. Each React component is encapsulated and can operate independently; this allows you to build complex UIs from simple components.
Inspecting the Starter Code
If you’re going to work on the tutorial in your browser, open this code in a new tab: Starter Code. If you’re going to work on the tutorial locally, instead open src/index.js
in your project folder.
This Starter Code is the base of what we’re building. We’ve provided the CSS styling so that you only need to focus on learning React.
By inspecting the code, you’ll notice that we have three React components:
- Square
- Board
- Game
The Square component renders a single <button>
and the Board renders 9 squares. The Game component renders a board with placeholder values which we’ll modify later. There are currently no interactive components.
In Board’s renderSquare
method, change the code to pass a prop called value
to the Square:
class Board extends React.Component {
renderSquare(i) {
return <Square value={i} />; }
}
Change Square’s render
method to show that value by replacing {/* TODO */}
with {this.props.value}
:
class Square extends React.Component {
render() {
return (
<button className="square">
{this.props.value} </button>
);
}
}
View full code here:
class Square extends React.Component {
render() {
return (
<button className="square">
{this.props.value}
</button>
);
}
}
class Board extends React.Component {
renderSquare(i) {
return <Square value={i} />;
}
render() {
const status = 'Next player: X';
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 {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{this.props.value}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
We now have the basic building blocks for our tic-tac-toe game. To have a complete game, we now need to alternate placing “X”s and “O”s on the board, and we need a way to determine a winner.
Let’s fill the Square component with an “X” when we click it. First, change the button tag that is returned from the Square component’s render()
function to this:
class Square extends React.Component {
render() {
return (
<button className="square" onClick={() => alert('click')}>
{this.props.value}
</button>
);
}
}
Notice how with onClick={() => alert('click')}
, we are passing a function as the onClick
prop. React will only call this function after a click. Forgetting () =>
and writing onClick={alert('click')}
is a common mistake, and would fire the alert every time the component re-renders.
As a next step, we want the Square component to “remember” that it got clicked, and fill it with an “X” mark. To “remember” things, components use state.
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
render() {
return (
<button className="square" onClick={() => alert('click')}>
{this.props.value}
</button>
);
}
}
Note
In JavaScript classes, you need to always call super
when defining the constructor of a subclass. All React component classes that have a constructor
should start with a super(props)
call.
Now we’ll change the Square’s render
method
- Replace
this.props.value
withthis.state.value
inside the<button>
tag. - Replace the
onClick={...}
event handler withonClick={() => this.setState({value: 'X'})}
. - Put the
className
andonClick
props on separate lines for better readability.
It should look something like this:
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
render() {
return (
<button
className="square"
onClick={() => this.setState({value: 'X'})}
>
{this.state.value}
</button>
);
}
}
class Board extends React.Component {
renderSquare(i) {
return <Square />;
}
render() {
const status = 'Next player: X';
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 {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
By calling this.setState
from an onClick
handler in the Square’s render
method, we are asking React to re-render that Square whenever its <button>
is clicked. After the update, the Square’s this.state.value
will be 'X'
, so we’ll see the X
on the game board. If you click on any Square, an X
should show up.
When you call setState
in a component, React automatically updates the child components inside of it too.
Add a constructor to the Board and set the Board’s initial state to contain an array of 9 nulls corresponding to the 9 squares:
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
};
}
renderSquare(i) {
return <Square value={i} />;
}
It should look something like this:
[
'O', null, 'X',
'X', 'X', 'O',
'O', null, null,
]
The render method should look like this:
renderSquare(i) {
return <Square value={i} />;
}
We will now use the prop passing mechanism again. We will modify the Board to instruct each individual Square about its current value ('X'
, 'O'
, or null
). We have already defined the squares
array in the Board’s constructor, and we will modify the Board’s renderSquare
method to read from it:
class Square extends React.Component {
// TODO: remove the constructor
constructor(props) {
super(props);
this.state = {
value: null,
};
}
render() {
// TODO: use onClick={this.props.onClick}
// TODO: replace this.state.value with this.props.value
return (
<button className="square" onClick={() => this.setState({value: 'X'})}>
{this.state.value}
</button>
);
}
}
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
};
}
renderSquare(i) {
return <Square value={this.state.squares[i]} />;
}
render() {
const status = 'Next player: X';
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 {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
We’ll change the renderSquare
method in Board to:
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
Now we’re passing down two props from Board to Square: value
and onClick
Replace this.state.value
with this.props.value
Replace this.setState()
with this.props.onClick()
Delete the constructor
from Square
The component should look something like this:
class Square extends React.Component {
render() {
return (
<button
className="square"
onClick={() => this.props.onClick()}
>
{this.props.value}
</button>
);
}
}
We’ll now add handleClick
to the Board class:
class Square extends React.Component {
render() {
return (
<button
className="square"
onClick={() => this.props.onClick()}
>
{this.props.value}
</button>
);
}
}
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
};
}
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});
}
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
render() {
const status = 'Next player: X';
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 {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
Function Components
We’ll now change the Square to be a function component.
function components are a simpler way to write components
Instead of defining a class which extends React.Component
, we can write a function that takes props
as input and returns what should be rendered. Function components are easy to write, and many components can be expressed this way.
Replace the Square class with this function:
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
Taking Turns
We’ll set the first move to be “X” by default. We can set this default by modifying the initial state in our Board constructor:
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
};
}
Each time a player moves, xIsNext
(a boolean) will be flipped to determine which player goes next and the game’s state will be saved. We’ll update the Board’s handleClick
function to flip the value of xIsNext
:
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
Declaring a Winner
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;
}
We will call calculateWinner(squares)
in the Board’s render
function to check if a player has won. If a player has won, we can display text such as “Winner: X” or “Winner: O”. We’ll replace the status
declaration in Board’s render
function with this code:
render() {
const winner = calculateWinner(this.state.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
We can now change the Board’s handleClick
function
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
Storing a History of Moves
We’ll store the past squares
arrays in another array called history
. The history
array represents all board states, from the first to the last move, and has a shape like this:
history = [
// Before first move
{
squares: [
null, null, null,
null, null, null,
null, null, null,
]
},
// After first move
{
squares: [
null, null, null,
null, 'X', null,
null, null, null,
]
},
// After second move
{
squares: [
null, null, null,
null, 'X', null,
null, null, 'O',
]
},
// ...
]
We’ll want the top-level Game component to display a list of past moves. It will need access to the history
to do that, so we will place the history
state in the top-level Game component.
Set up the initial state for the Game component within its constructor:
- Delete the
constructor
in Board. - Replace
this.state.squares[i]
withthis.props.squares[i]
- Replace
this.handleClick(i)
withthis.props.onClick(i)
We’ll update the Game component’s render
function to use the most recent history entry to determine and display the game’s status:
Since the Game component is now rendering the game’s status, we can remove the corresponding code from the Board’s render
method. After refactoring, the Board’s render
function looks like this:
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>
);
}
Since we are recording the tic-tac-toe game’s history, we can now display it to the player as a list of past moves.
Using the map method. We are going to map our history moves
const numbers = [1, 2, 3];
const doubled = numbers.map(x => x * 2); // [2, 4, 6]
It’s strongly recommended that you assign proper keys whenever you build dynamic lists.
Implementing
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>
);
});
First, add stepNumber: 0
to the initial state in Game’s constructor
:
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [{
squares: Array(9).fill(null),
}],
stepNumber: 0,
xIsNext: true,
};
}
Next, we’ll define the jumpTo
method in Game to update that stepNumber
.
handleClick(i) {
// this method has not changed
}
jumpTo(step) {
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0,
});
}
render() {
// this method has not changed
}
Replace
reading this.state.history
with this.state.history.slice(0, this.state.stepNumber + 1)
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
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
}]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext,
});
}
Modify the Game component’s render
method from always rendering the last move to rendering the currently selected move according to stepNumber
:
render() {
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
}