Browse Source

Improve tutorial.md's clarity and structure (#649)

* Improve tutorial.md's clarity and structure

* Fix typos and make additional changes to tutorial.md

* edits

* moar changes
main
Jackie Lang 7 years ago
committed by Dan Abramov
parent
commit
4d575e7090
  1. 71
      content/tutorial/nav.yml
  2. 393
      content/tutorial/tutorial.md

71
content/tutorial/nav.yml

@ -1,21 +1,30 @@
- title: Tutorial - title: Tutorial
items: items:
- id: before-we-start - id: before-we-start-the-tutorial
title: Before We Start title: Before We Start the Tutorial
href: /tutorial/tutorial.html#before-we-start href: /tutorial/tutorial.html#before-we-start-the-tutorial
forceInternal: true forceInternal: true
subitems: subitems:
- id: what-were-building - id: what-were-building
title: What We're Building title: What Are We Building?
href: /tutorial/tutorial.html#what-were-building href: /tutorial/tutorial.html#what-are-we-building
forceInternal: true forceInternal: true
- id: prerequisites - id: prerequisites
title: Prerequisites title: Prerequisites
href: /tutorial/tutorial.html#prerequisites href: /tutorial/tutorial.html#prerequisites
forceInternal: true forceInternal: true
- id: how-to-follow-along - id: setup-for-the-tutorial
title: How to Follow Along title: Setup for the Tutorial
href: /tutorial/tutorial.html#how-to-follow-along href: /tutorial/tutorial.html#setup-for-the-tutorial
forceInternal: true
subitems:
- id: setup-option-1-write-code-in-the-browser
title: "Option 1: Write Code in the Browser"
href: /tutorial/tutorial.html#setup-option-1-write-code-in-the-browser
forceInternal: true
- id: setup-option-2-local-development-environment
title: "Option 2: Local Development Environment"
href: /tutorial/tutorial.html#setup-option-2-local-development-environment
forceInternal: true forceInternal: true
- id: help-im-stuck - id: help-im-stuck
title: Help, I'm Stuck! title: Help, I'm Stuck!
@ -27,30 +36,34 @@
forceInternal: true forceInternal: true
subitems: subitems:
- id: what-is-react - id: what-is-react
title: What is React? title: What Is React?
href: /tutorial/tutorial.html#what-is-react href: /tutorial/tutorial.html#what-is-react
forceInternal: true forceInternal: true
- id: getting-started - id: inspecting-the-starter-code
title: Getting Started title: Inspecting the Starter Code
href: /tutorial/tutorial.html#getting-started href: /tutorial/tutorial.html#inspecting-the-starter-code
forceInternal: true forceInternal: true
- id: passing-data-through-props - id: passing-data-through-props
title: Passing Data Through Props title: Passing Data Through Props
href: /tutorial/tutorial.html#passing-data-through-props href: /tutorial/tutorial.html#passing-data-through-props
forceInternal: true forceInternal: true
- id: an-interactive-component - id: making-an-interactive-component
title: An Interactive Component title: Making an Interactive Component
href: /tutorial/tutorial.html#an-interactive-component href: /tutorial/tutorial.html#making-an-interactive-component
forceInternal: true forceInternal: true
- id: developer-tools - id: developer-tools
title: Developer Tools title: Developer Tools
href: /tutorial/tutorial.html#developer-tools href: /tutorial/tutorial.html#developer-tools
forceInternal: true forceInternal: true
- id: completing-the-game
title: Completing the Game
href: /tutorial/tutorial.html#completing-the-game
forceInternal: true
subitems:
- id: lifting-state-up - id: lifting-state-up
title: Lifting State Up title: Lifting State Up
href: /tutorial/tutorial.html#lifting-state-up href: /tutorial/tutorial.html#lifting-state-up
forceInternal: true forceInternal: true
subitems:
- id: why-immutability-is-important - id: why-immutability-is-important
title: Why Immutability Is Important title: Why Immutability Is Important
href: /tutorial/tutorial.html#why-immutability-is-important href: /tutorial/tutorial.html#why-immutability-is-important
@ -67,18 +80,26 @@
title: Declaring a Winner title: Declaring a Winner
href: /tutorial/tutorial.html#declaring-a-winner href: /tutorial/tutorial.html#declaring-a-winner
forceInternal: true forceInternal: true
- id: storing-a-history - id: adding-time-travel
title: Storing A History title: Adding Time Travel
href: /tutorial/tutorial.html#storing-a-history href: /tutorial/tutorial.html#adding-time-travel
forceInternal: true forceInternal: true
subitems: subitems:
- id: showing-the-moves - id: storing-a-history-of-moves
title: Showing the Moves title: Storing a History of Moves
href: /tutorial/tutorial.html#showing-the-moves href: /tutorial/tutorial.html#storing-a-history-of-moves
forceInternal: true
- id: lifting-state-up-again
title: Lifting State Up, Again
href: /tutorial/tutorial.html#lifting-state-up-again
forceInternal: true
- id: showing-the-past-moves
title: Showing the Past Moves
href: /tutorial/tutorial.html#showing-the-past-moves
forceInternal: true forceInternal: true
- id: keys - id: picking-a-key
title: Keys title: Picking a Key
href: /tutorial/tutorial.html#keys href: /tutorial/tutorial.html#picking-a-key
forceInternal: true forceInternal: true
- id: implementing-time-travel - id: implementing-time-travel
title: Implementing Time Travel title: Implementing Time Travel

393
content/tutorial/tutorial.md

@ -12,45 +12,41 @@ redirect_from:
- "docs/tutorial-zh-CN.html" - "docs/tutorial-zh-CN.html"
--- ---
## Before We Start ## Before We Start the Tutorial
### What We're Building ### What Are We Building?
Today, we're going to build an interactive tic-tac-toe game. In this tutorial, we'll show how to build an interactive tic-tac-toe game with React.
If you like, you can check out the final result here: [Final Result](https://codepen.io/gaearon/pen/gWWZgR?editors=0010). Don't worry if the code doesn't make sense to you yet, or if it uses an unfamiliar syntax. We will be learning how to build this game step by step throughout this tutorial. You can see what we'll be building here: [Final Result](https://codepen.io/gaearon/pen/gWWZgR?editors=0010). If the code doesn't make sense to you, or if you are unfamiliar with the code's syntax, don't worry! The goal of this tutorial is to help you understand React and its syntax.
Try playing the game. You can also click on a button in the move list to go "back in time" and see what the board looked like just after that move was made. We recommend that you check out the tic-tac-toe game before continuing with the tutorial. One of the features that you'll notice is that there is a numbered list to the right of the game's board. This list gives you a history of all of the moves that have occurred in the game, and is updated as the game progresses.
Once you get a little familiar with the game, feel free to close that tab, as we'll start from a simpler template in the next sections. You can close the tic-tac-toe game once you're familiar with it. We'll be starting from a simpler template in this tutorial. Our next step is to set you up so that you can start building the game.
### Prerequisites ### Prerequisites
We'll assume some familiarity with HTML and JavaScript, but you should be able to follow along even if you haven't used them before. We'll assume that you have some familiarity with HTML and JavaScript, but you should be able to follow along even if you're coming from a different programming language. We'll also assume that you're familiar with programming concepts like functions, objects, arrays, and to a lesser extent, classes.
If you need a refresher on JavaScript, we recommend reading [this guide](https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript). Note that we're also using some features from ES6, a recent version of JavaScript. In this tutorial, we're using [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), [classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes), [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let), and [`const`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) statements. You can use the [Babel REPL](babel://es5-syntax-example) to check what ES6 code compiles to. If you need to review JavaScript, we recommend reading [this guide](https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript). Note that we're also using some features from ES6 -- a recent version of JavaScript. In this tutorial, we're using [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), [classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes), [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let), and [`const`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) statements. You can use the [Babel REPL](babel://es5-syntax-example) to check what ES6 code compiles to.
### How to Follow Along ## Setup for the Tutorial
There are two ways to complete this tutorial: you could either write the code right in the browser, or you could set up a local development environment on your machine. You can choose either option depending on what you feel comfortable with. There are two ways to complete this tutorial: you can either write the code in your browser, or you can set up a local development environment on your computer.
#### If You Prefer to Write Code in the Browser ### Setup Option 1: Write Code in the Browser
This is the quickest way to get started! This is the quickest way to get started!
First, open this [starter code](https://codepen.io/gaearon/pen/oWWQNa?editors=0010) in a new tab. It should display an empty tic-tac-toe field. We will be editing that code during this tutorial. First, open this [starter code](https://codepen.io/gaearon/pen/oWWQNa?editors=0010) in a new tab. The new tab should display an empty tic-tac-toe game board and React code. We will be editing the React code in this tutorial.
You can now skip the next section about setting up a local development environment and head straight to the [overview](#overview). You can now skip the second setup option, and go to the [Overview](#overview) section to get an overview of React.
#### If You Prefer to Write Code in Your Editor ### Setup Option 2: Local Development Environment
Alternatively, you can set up a project on your computer. **This is completely optional and not required for this tutorial!**
Note: **this is completely optional and not required for this tutorial!** This setup requires more work but allows you to complete the tutorial using an editor of your choice. Here are the steps to follow:
This is more work, but lets you work from the comfort of your editor.
If you want to do it, here are the steps to follow:
1. Make sure you have a recent version of [Node.js](https://nodejs.org/en/) installed. 1. Make sure you have a recent version of [Node.js](https://nodejs.org/en/) installed.
2. Follow the [installation instructions](/docs/add-react-to-a-new-app.html) to create a new project. 2. Follow the [installation instructions](/docs/add-react-to-a-new-app.html) to create a new project.
@ -85,15 +81,15 @@ We recommend following [these instructions](http://babeljs.io/docs/editors) to c
### Help, I'm Stuck! ### Help, I'm Stuck!
If you get stuck, check out the [community support resources](/community/support.html). In particular, [Reactiflux chat](/community/support.html#reactiflux-chat) is a great way to get quick help. If you don't get a good answer anywhere, please file an issue, and we'll help you out. If you get stuck, check out the [community support resources](/community/support.html). In particular, [Reactiflux Chat](/community/support.html#reactiflux-chat) is a great way to get help quickly. If you don't receive an answer, or if you remain stuck, please file an issue, and we'll help you out.
With this out of the way, let's get started!
## Overview ## Overview
### What is React? Now that you're set up, let's get an overview of React!
React is a declarative, efficient, and flexible JavaScript library for building user interfaces. ### What Is React?
React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It lets you compose complex UIs from small and isolated pieces of code called "components".
React has a few different kinds of components, but we'll start with `React.Component` subclasses: React has a few different kinds of components, but we'll start with `React.Component` subclasses:
@ -116,11 +112,11 @@ class ShoppingList extends React.Component {
// Example usage: <ShoppingList name="Mark" /> // Example usage: <ShoppingList name="Mark" />
``` ```
We'll get to the funny XML-like tags in a second. Your components tell React what you want to render – then React will efficiently update and render just the right components when your data changes. We'll get to the funny XML-like tags soon. We use components to tell React what we want to see on the screen. When our data changes, React will efficiently update and re-render our components.
Here, ShoppingList is a **React component class**, or **React component type**. A component takes in parameters, called `props`, and returns a hierarchy of views to display via the `render` method. Here, ShoppingList 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 render, and then React takes that description and renders it to the screen. In particular, `render` returns a **React element**, which is a lightweight description of what to render. Most React developers use a special syntax called JSX which makes it easier to write these structures. The `<div />` syntax is transformed at build time to `React.createElement('div')`. The example above is equivalent to: 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. Most React developers use a special syntax called "JSX" which makes these structures easier to write. The `<div />` syntax is transformed at build time to `React.createElement('div')`. The example above is equivalent to:
```javascript ```javascript
return React.createElement('div', {className: 'shopping-list'}, return React.createElement('div', {className: 'shopping-list'},
@ -131,31 +127,33 @@ return React.createElement('div', {className: 'shopping-list'},
[See full expanded version.](babel://tutorial-expanded-version) [See full expanded version.](babel://tutorial-expanded-version)
If you're curious, `createElement()` is described in more detail in the [API reference](/docs/react-api.html#createelement), but we won't be using it directly in this tutorial. Instead, we will keep using JSX. If you're curious, `createElement()` is described in more detail in the [API reference](/docs/react-api.html#createelement), but we won't be using it in this tutorial. Instead, we will keep using JSX.
JSX comes with the full power of JavaScript. You can put *any* JavaScript expressions within braces inside JSX. Each React element is a JavaScript object that you can store in a variable or pass around in your program.
You can put any JavaScript expression within braces inside JSX. Each React element is a real JavaScript object that you can store in a variable or pass around your program. The `ShoppingList` 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 shopping list by writing `<ShoppingList />`. Each React component is encapsulated and can operate independently; this allows you to build complex UIs from simple components.
The `ShoppingList` component only renders built-in DOM components, but you can compose custom React components just as easily, by writing `<ShoppingList />`. Each component is encapsulated so it can operate independently, which allows you to build complex UIs out of simple components. ## Inspecting the Starter Code
### Getting Started **If you're going to work on the tutorial in your browser,** open this code in a new tab: [Starter Code](https://codepen.io/gaearon/pen/oWWQNa?editors=0010).
Start with this example: [Starter Code](https://codepen.io/gaearon/pen/oWWQNa?editors=0010). **If you're going to work on the tutorial locally,** open `src/index.js` in your project folder (you have already touched this file during the [setup](#setup-option-2-local-development-environment)).
It contains the shell of what we're building today. We've provided the styles so you only need to worry about the JavaScript. This Starter Code is the base of what we're building. We've provided the CSS styling so that you only need focus on learning React and programming the tic-tac-toe game.
In particular, we have three components: By inspecting the code, you'll notice that we have three React components:
* Square * Square
* Board * Board
* Game * Game
The Square component renders a single `<button>`, the Board renders 9 squares, and the Game component renders a board with some placeholders that we'll fill in later. None of the components are interactive at this point. 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.
### Passing Data Through Props ### Passing Data Through Props
Just to get our feet wet, let's try passing some data from the Board component to the Square component. Just to get our feet wet, let's try passing some data from our Board component to our Square component.
In Board's `renderSquare` method, change the code to pass a `value` prop to the Square: In Board's `renderSquare` method, change the code to pass a prop called `value` to the Square:
```js{3} ```js{3}
class Board extends React.Component { class Board extends React.Component {
@ -164,7 +162,7 @@ class Board extends React.Component {
} }
``` ```
Then change Square's `render` method to show that value by replacing `{/* TODO */}` with `{this.props.value}`: Change Square's `render` method to show that value by replacing `{/* TODO */}` with `{this.props.value}`:
```js{5} ```js{5}
class Square extends React.Component { class Square extends React.Component {
@ -186,11 +184,14 @@ After: You should see a number in each square in the rendered output.
![React Devtools](../images/tutorial/tictac-numbers.png) ![React Devtools](../images/tutorial/tictac-numbers.png)
[View the current code.](https://codepen.io/gaearon/pen/aWWQOG?editors=0010) **[View the current code.](https://codepen.io/gaearon/pen/aWWQOG?editors=0010)**
### An Interactive Component Congratulations! You've just "passed a prop" from a parent Board component to a child Square component. Passing props is how information flows in React apps, from parents to children.
Let's make the Square component fill in an "X" when you click it. Try changing the button tag returned in the `render()` function of the Square like this: ### Making an Interactive Component
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:
```javascript{4} ```javascript{4}
class Square extends React.Component { class Square extends React.Component {
@ -204,13 +205,17 @@ class Square extends React.Component {
} }
``` ```
If you click on a square now, you should get an alert in your browser. If we click on a Square now, we should get an alert in our browser. The `onClick` assignment uses the new JavaScript [arrow function](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions) syntax.
>Note
>
>With `onClick={() => alert('click')}`, we're passing *a function* as the `onClick` prop. Passing `onClick={alert('click')}` would fire the alert immediately instead of when a Square is clicked.
This uses the new JavaScript [arrow function](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions) syntax. Note that we're passing a function as the `onClick` prop. Doing `onClick={alert('click')}` would alert immediately instead of when the button is clicked. We want the Square component to "remember" when it gets clicked, and fill itself with an "X" mark. To "remember" things, components need **state**.
React components can have state by setting `this.state` in the constructor, which should be considered private to the component. Let's store the current value of the square in state, and change it when the square is clicked. React components can have state by setting `this.state` in their constructors. `this.state` should be considered as private to a React component that it's defined in. Let's store the current value of the Square in `this.state`, and change it when the Square is clicked.
First, add a constructor to the class to initialize the state: First, we'll add a constructor to the class to initialize the state:
```javascript{2-7} ```javascript{2-7}
class Square extends React.Component { class Square extends React.Component {
@ -231,16 +236,19 @@ class Square extends React.Component {
} }
``` ```
In [JavaScript classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes), you need to explicitly call `super();` when defining the constructor of a subclass. >Note
>
>In [JavaScript classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes), you need to always call `super` when defining the constructor of a subclass. All React component classes that have a `constructor` should start it with a `super(props)` call.
Now change the Square `render` method to display the value from the current state, and to toggle it on click: Now we'll change the Square's `render` method to display the current state's value when clicked:
* Replace `this.props.value` with `this.state.value` inside the `<button>` tag. * Replace `this.props.value` with `this.state.value` inside the `<button>` tag.
* Replace the `() => alert()` event handler with `() => this.setState({value: 'X'})`. * Replace the `() => alert()` event handler with `() => this.setState({value: 'X'})`.
* Put the `className` and `onClick` props on separate lines for better readability.
Now the `<button>` tag looks like this: After these changes, the `<button>` tag that is returned by the Square's `render` method looks like this:
```javascript{10-12} ```javascript{12-13,15}
class Square extends React.Component { class Square extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -251,7 +259,10 @@ class Square extends React.Component {
render() { render() {
return ( return (
<button className="square" onClick={() => this.setState({value: 'X'})}> <button
className="square"
onClick={() => this.setState({value: 'X'})}
>
{this.state.value} {this.state.value}
</button> </button>
); );
@ -259,21 +270,21 @@ class Square extends React.Component {
} }
``` ```
Whenever `this.setState` is called, an update to the component is scheduled, causing React to merge in the passed state update and rerender the component along with its descendants. When the component rerenders, `this.state.value` will be `'X'` so you'll see an X in the grid. By calling `this.setState` from an `onClick` handler in the Square's `render` method, we tell 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.
If you click on any square, an X should show up in it. When you call `setState` in a component, React automatically updates the child components inside of it too.
[View the current code.](https://codepen.io/gaearon/pen/VbbVLg?editors=0010) **[View the current code.](https://codepen.io/gaearon/pen/VbbVLg?editors=0010)**
### Developer Tools ### Developer Tools
The React Devtools extension for [Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/react-devtools/) lets you inspect a React component tree in your browser devtools. The React Devtools extension for [Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/react-devtools/) lets you inspect a React component tree with your browser's developer tools.
<img src="../images/tutorial/devtools.png" alt="React Devtools" style="max-width: 100%"> <img src="../images/tutorial/devtools.png" alt="React Devtools" style="max-width: 100%">
It lets you inspect the props and state of any of the components in your tree. The React DevTools let you check the props and the state of your React components.
After installing it, you can right-click any element on the page, click "Inspect" to open the developer tools, and the React tab will appear as the last tab to the right. After installing React DevTools, you can right-click on any element on the page, click "Inspect" to open the developer tools, and the React tab will appear as the last tab to the right.
**However, note there are a few extra steps to get it working with CodePen:** **However, note there are a few extra steps to get it working with CodePen:**
@ -282,17 +293,19 @@ After installing it, you can right-click any element on the page, click "Inspect
3. Click "Change View" and then choose "Debug mode". 3. Click "Change View" and then choose "Debug mode".
4. In the new tab that opens, the devtools should now have a React tab. 4. In the new tab that opens, the devtools should now have a React tab.
## Lifting State Up ## Completing the Game
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.
We now have the basic building blocks for a tic-tac-toe game. But right now, the state is encapsulated in each Square component. To make a fully-working game, we now need to check if one player has won the game, and alternate placing X and O in the squares. To check if someone has won, we'll need to have the value of all 9 squares in one place, rather than split up across the Square components. ### Lifting State Up
You might think that Board should just inquire what the current state of each Square is. Although it is technically possible to do this in React, it is discouraged because it tends to make code difficult to understand, more brittle, and harder to refactor. Currently, each Square component maintains the game's state. To check for a winner, we'll maintain the value of each of the 9 squares in one location.
Instead, the best solution here is to store this state in the Board component instead of in each Square – and the Board component can tell each Square what to display, like how we made each square display its index earlier. We may think that Board should just ask each Square for the Square's state. Although this approach is possible in React, we discourage it because the code becomes difficult to understand, susceptible to bugs, and hard to refactor. Instead, the best approach is to store the game's state in the parent Board component instead of in each Square. The Board component can tell each Square what to display by passing a prop, [just like we did when we passed a number to each Square](#passing-data-through-props).
**When you want to aggregate data from multiple children or to have two child components communicate with each other, move the state upwards so that it lives in the parent component. The parent can then pass the state back down to the children via props, so that the child components are always in sync with each other and with the parent.** **To collect data from multiple children, or to have two child components communicate with each other, you need to declare the shared state in their parent component instead. The parent component can pass the state back down to the children by using props; this keeps the child components in sync with each other and with the parent component.**
Pulling state upwards like this is common when refactoring React components, so let's take this opportunity to try it out. Add a constructor to the Board and set its initial state to contain an array with 9 nulls, corresponding to the 9 squares: Lifting state into a parent component is common when React components are refactored -- let's take this opportunity to try it out. We'll add a constructor to the Board and set the Board's initial state to contain an array with 9 nulls. These 9 nulls correspond to the 9 squares:
```javascript{2-7} ```javascript{2-7}
class Board extends React.Component { class Board extends React.Component {
@ -334,7 +347,7 @@ class Board extends React.Component {
} }
``` ```
We'll fill it in later so that a board looks something like When we fill the board in later, the board will look something like this:
```javascript ```javascript
[ [
@ -344,7 +357,7 @@ We'll fill it in later so that a board looks something like
] ]
``` ```
Board's `renderSquare` method currently looks like this: The Board's `renderSquare` method currently looks like this:
```javascript ```javascript
renderSquare(i) { renderSquare(i) {
@ -352,7 +365,9 @@ Board's `renderSquare` method currently looks like this:
} }
``` ```
Modify it to pass a `value` prop to Square. In the beginning, we [passed the `value` prop down](#passing-data-through-props) from the Board to show numbers from 1 to 9 in every Square. In a different previous step, we replaced the numbers with an "X" mark [determined by Square's own state](#making-an-interactive-component). This is why Square currently ignores the `value` prop passed to it by the Board.
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:
```javascript{2} ```javascript{2}
renderSquare(i) { renderSquare(i) {
@ -360,11 +375,13 @@ Modify it to pass a `value` prop to Square.
} }
``` ```
[View the current code.](https://codepen.io/gaearon/pen/gWWQPY?editors=0010) **[View the current code.](https://codepen.io/gaearon/pen/gWWQPY?editors=0010)**
Each Square will now receive a `value` prop that will either be `'X'`, `'O'`, or `null` for empty squares.
Now we need to change what happens when a square is clicked. The Board component now stores which squares are filled, which means we need some way for Square to update the state of Board. Since component state is considered private, we can't update Board's state directly from Square. Next, we need to change what happens when a Square is clicked. The Board component now maintains which squares are filled. We need to create a way for the Square to update the Board's state. Since state is considered to be private to a component that defines it, we cannot update the Board's state directly from Square.
The usual pattern here is pass down a function from Board to Square that gets called when the square is clicked. Change `renderSquare` in Board again so that it reads: To maintain the Board's state's privacy, we'll pass down a function from the Board to the Square. This function will get called when a Square is clicked. We'll change the `renderSquare` method in Board to:
```javascript{5} ```javascript{5}
renderSquare(i) { renderSquare(i) {
@ -377,21 +394,26 @@ The usual pattern here is pass down a function from Board to Square that gets ca
} }
``` ```
We split the returned element into multiple lines for readability, and added parentheses around it so that JavaScript doesn't insert a semicolon after `return` and break our code. >Note
>
>We split the returned element into multiple lines for readability, and added parentheses so that JavaScript doesn't insert a semicolon after `return` and break our code.
Now we're passing down two props from Board to Square: `value` and `onClick`. The latter is a function that Square can call. Let's make the following changes to Square: Now we're passing down two props from Board to Square: `value` and `onClick`. The `onClick` prop is a function that Square can call when clicked. We'll make the following changes to Square:
* Replace `this.state.value` with `this.props.value` in Square's `render`. * Replace `this.state.value` with `this.props.value` in Square's `render` method
* Replace `this.setState()` with `this.props.onClick()` in Square's `render`. * Replace `this.setState()` with `this.props.onClick()` in Square's `render` method
* Delete `constructor` definition from Square because it doesn't have state anymore. * Delete the `constructor` from Square because Square no longer keeps track of the game's state
After these changes, the whole Square component looks like this: After these changes, the Square component looks like this:
```javascript{1,2,4,5} ```javascript{1,2,6,8}
class Square extends React.Component { class Square extends React.Component {
render() { render() {
return ( return (
<button className="square" onClick={() => this.props.onClick()}> <button
className="square"
onClick={() => this.props.onClick()}
>
{this.props.value} {this.props.value}
</button> </button>
); );
@ -399,17 +421,19 @@ class Square extends React.Component {
} }
``` ```
Now when the square is clicked, it calls the `onClick` function that was passed by Board. Let's recap what happens here: When a Square is clicked, the `onClick` function provided by the Board is called. Here's a review of how this is achieved:
1. The `onClick` prop on the built-in DOM `<button>` component tells React to set up a click event listener. 1. The `onClick` prop on the built-in DOM `<button>` component tells React to set up a click event listener.
2. When the button is clicked, React will call the `onClick` event handler defined in Square's `render()` method. 2. When the button is clicked, React will call the `onClick` event handler that is defined in Square's `render()` method.
3. This event handler calls `this.props.onClick()`. Square's props were specified by the Board. 3. This event handler calls `this.props.onClick()`. The Square's `onClick` prop was specified by the Board.
4. Board passed `onClick={() => this.handleClick(i)}` to Square, so, when called, it runs `this.handleClick(i)` on the Board. 4. Since the Board passed `onClick={() => this.handleClick(i)}` to Square, the Square calls `this.handleClick(i)` when clicked.
5. We have not defined the `handleClick()` method on the Board yet, so the code crashes. 5. We have not defined the `handleClick()` method yet, so our code crashes.
Note that DOM `<button>` element's `onClick` attribute has a special meaning to React, but we could have named Square's `onClick` prop or Board's `handleClick` method differently. It is, however, conventional in React apps to use `on*` names for the attributes and `handle*` for the handler methods. >Note
>
>The DOM `<button>` element's `onClick` attribute has a special meaning to React because it is a built-in component. For custom components like Square, the naming is up to you. We could name the Square's `onClick` prop or Board's `handleClick` method differently. In React, however, it is a convention to use `on[Event]` names for props which represent events and `handle[Event]` for the methods which handle the events.
Try clicking a square – you should get an error because we haven't defined `handleClick` yet. Add it to the Board class. When we try to click a Square, we should get an error because we haven't defined `handleClick` yet. We'll now add `handleClick` to the Board class:
```javascript{9-13} ```javascript{9-13}
class Board extends React.Component { class Board extends React.Component {
@ -462,28 +486,28 @@ class Board extends React.Component {
} }
``` ```
[View the current code.](https://codepen.io/gaearon/pen/ybbQJX?editors=0010) **[View the current code.](https://codepen.io/gaearon/pen/ybbQJX?editors=0010)**
We call `.slice()` to copy the `squares` array instead of mutating the existing array. Jump ahead a [section](/tutorial/tutorial.html#why-immutability-is-important) to learn why immutability is important. After these changes, we're again able to click on the Squares to fill them. However, now the state is stored in the Board component instead of the individual Square components. When the Board's state changes, the Square components re-render automatically. Keeping the state of all squares in the Board component will allow it to determine the winner in the future.
Now you should be able to click in squares to fill them again, but the state is stored in the Board component instead of in each Square, which lets us continue building the game. Note how whenever Board's state changes, the Square components rerender automatically. Since the Square components no longer maintain state, the Square components receive values from the Board component and inform the Board component when they're clicked. In React terms, the Square components are now **controlled components**. The Board has full control over them.
Square no longer keeps its own state; it receives its value from its parent Board and informs its parent when it's clicked. We call components like this **controlled components**. Note how in `handleClick`, we call `.slice()` to create a copy the `squares` array to modify instead of modifying the existing array. We will explain why we create a copy of the `squares` array in the next section.
### Why Immutability Is Important ### Why Immutability Is Important
In the previous code example, we suggest using the `.slice()` operator to copy the `squares` array prior to making changes and to prevent mutating the existing array. Let's talk about what this means and why it is an important concept to learn. In the previous code example, we suggested that you use the `.slice()` operator to create a copy the `squares` array to modify instead of modifying the existing array. We'll now discuss immutability and why immutability is important to learn.
There are generally two ways for changing data. The first method is to *mutate* the data by directly changing the values of a variable. The second method is to replace the data with a new copy of the object that also includes desired changes. There are generally two approaches to changing data. The first approach is to *mutate* the data by directly changing the data's values. The second approach is to replace the data with a new copy which has the desired changes.
#### Data change with mutation #### Data Change with Mutation
```javascript ```javascript
var player = {score: 1, name: 'Jeff'}; var player = {score: 1, name: 'Jeff'};
player.score = 2; player.score = 2;
// Now player is {score: 2, name: 'Jeff'} // Now player is {score: 2, name: 'Jeff'}
``` ```
#### Data change without mutation #### Data Change without Mutation
```javascript ```javascript
var player = {score: 1, name: 'Jeff'}; var player = {score: 1, name: 'Jeff'};
@ -494,29 +518,31 @@ var newPlayer = Object.assign({}, player, {score: 2});
// var newPlayer = {...player, score: 2}; // var newPlayer = {...player, score: 2};
``` ```
The end result is the same but by not mutating (or changing the underlying data) directly we now have an added benefit that can help us increase component and overall application performance. The end result is the same but by not mutating (or changing the underlying data) directly, we gain several benefits described below.
#### Easier Undo/Redo and Time Travel #### Complex Features Become Simple
Immutability also makes some complex features much easier to implement. For example, further in this tutorial we will implement time travel between different stages of the game. Avoiding data mutations lets us keep a reference to older versions of the data, and switch between them if we need to. Immutability makes complex features much easier to implement. Later in this tutorial, we will implement a "time travel" feature that allows us to review the tic-tac-toe game's history and "jump back" to previous moves. This functionality isn't specific to games -- an ability to undo and redo certain actions is a common requirement in applications. Avoiding direct data mutation lets us keep previous versions of the game's history intact, and reuse them later.
#### Tracking Changes #### Detecting Changes
Determining if a mutated object has changed is complex because changes are made directly to the object. This then requires comparing the current object to a previous copy, traversing the entire object tree, and comparing each variable and value. This process can become increasingly complex. Detecting changes in mutable objects is difficult because they are modified directly. This detection requires the mutable object to be compared to previous copies of itself and the entire object tree to be traversed.
Determining how an immutable object has changed is considerably easier. If the object being referenced is different from before, then the object has changed. That's it. Detecting changes in immutable objects is considerably easier. If the immutable object that is being referenced is different than the previous one, then the object has changed.
#### Determining When to Re-render in React #### Determining When to Re-render in React
The biggest benefit of immutability in React comes when you build simple _pure components_. Since immutable data can more easily determine if changes have been made, it also helps to determine when a component requires being re-rendered. The main benefit of immutability is that it helps you build _pure components_ in React. Immutable data can easily determine if changes have been made which helps to determine when a component requires re-rendering.
To learn more about `shouldComponentUpdate()` and how you can build *pure components* take a look at [Optimizing Performance](/docs/optimizing-performance.html#examples). You can learn more about `shouldComponentUpdate()` and how you can build *pure components* by reading [Optimizing Performance](/docs/optimizing-performance.html#examples).
### Functional Components ### Functional Components
We've removed the constructor, and in fact, React supports a simpler syntax called **functional components** for component types like Square that only consist of a `render` method. Rather than define a class extending `React.Component`, simply write a function that takes props and returns what should be rendered. We'll now change the Square to be a **functional component**.
Replace the whole Square class with this function: In React, **functional components** are a simpler way to write components that only contain a `render` method and don't have their own state. 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. Functional components are less tedious to write than classes, and many components can be expressed this way.
Replace the Square class with this function:
```javascript ```javascript
function Square(props) { function Square(props) {
@ -528,17 +554,19 @@ function Square(props) {
} }
``` ```
You'll need to change `this.props` to `props` both times it appears. Many components in your apps will be able to be written as functional components: these components tend to be easier to write and React will optimize them more in the future. **[View the current code.](https://codepen.io/gaearon/pen/QvvJOv?editors=0010)**
While we're cleaning up the code, we also changed `onClick={() => props.onClick()}` to just `onClick={props.onClick}`, as passing the function down is enough for our example. Note that `onClick={props.onClick()}` would not work because it would call `props.onClick` immediately instead of passing it down. We have changed `this.props` to `props` both times it appears.
[View the current code.](https://codepen.io/gaearon/pen/QvvJOv?editors=0010) >Note
>
>When we modified the Square to be a functional component, we also changed `onClick={() => this.props.onClick()}` to a shorter `onClick={props.onClick}` (note the lack of parentheses on *both* sides). In a class, we used an arrow function to access the correct `this` value, but in a functional component we don't need to worry about `this`.
### Taking Turns ### Taking Turns
An obvious defect in our game is that only X can play. Let's fix that. We now need to fix an obvious defect in our tic-tac-toe game: the "O"s cannot be marked on the board.
Let's default the first move to be by 'X'. Modify our starting state in our Board constructor: We'll set the the first move to be "X" by default. We can set this default by modifying the initial state in our Board constructor:
```javascript{6} ```javascript{6}
class Board extends React.Component { class Board extends React.Component {
@ -551,7 +579,7 @@ class Board extends React.Component {
} }
``` ```
Each time we move we shall toggle `xIsNext` by flipping the boolean value and saving the state. Now update Board's `handleClick` function to flip the value of `xIsNext`: 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`:
```javascript{3,6} ```javascript{3,6}
handleClick(i) { handleClick(i) {
@ -564,7 +592,7 @@ Each time we move we shall toggle `xIsNext` by flipping the boolean value and sa
} }
``` ```
Now X and O take turns. Next, change the "status" text in Board's `render` so that it also displays who is next: With this change, "X"s and "O"s can take turns. Let's also change the "status" text in Board's `render` so that it displays which player has the next turn:
```javascript{2} ```javascript{2}
render() { render() {
@ -574,7 +602,7 @@ Now X and O take turns. Next, change the "status" text in Board's `render` so th
// the rest has not changed // the rest has not changed
``` ```
After these changes you should have this Board component: After applying these changes, you should have this Board component:
```javascript{6,11-16,29} ```javascript{6,11-16,29}
class Board extends React.Component { class Board extends React.Component {
@ -631,11 +659,11 @@ class Board extends React.Component {
} }
``` ```
[View the current code.](https://codepen.io/gaearon/pen/KmmrBy?editors=0010) **[View the current code.](https://codepen.io/gaearon/pen/KmmrBy?editors=0010)**
### Declaring a Winner ### Declaring a Winner
Let's show when a game is won. Add this helper function to the end of the file: Now that we show which player's turn is next, we should also show when the game is won and there are no more turns to make. We can determine a winner by adding this helper function to the end of the file:
```javascript ```javascript
function calculateWinner(squares) { function calculateWinner(squares) {
@ -659,9 +687,7 @@ function calculateWinner(squares) {
} }
``` ```
You can call it in Board's `render` function to check if anyone has won the game and make the status text show "Winner: [X/O]" when someone wins. 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:
Replace the `status` declaration in Board's `render` with this code:
```javascript{2-8} ```javascript{2-8}
render() { render() {
@ -677,7 +703,7 @@ Replace the `status` declaration in Board's `render` with this code:
// the rest has not changed // the rest has not changed
``` ```
You can now change `handleClick` in Board to return early and ignore the click if someone has already won the game or if a square is already filled: We can now change the Board's `handleClick` function to return early by ignoring a click if someone has won the game or if a Square is already filled:
```javascript{3-5} ```javascript{3-5}
handleClick(i) { handleClick(i) {
@ -693,18 +719,25 @@ You can now change `handleClick` in Board to return early and ignore the click i
} }
``` ```
Congratulations! You now have a working tic-tac-toe game. And now you know the basics of React. So *you're* probably the real winner here. **[View the current code.](https://codepen.io/gaearon/pen/LyyXgK?editors=0010)**
Congratulations! You now have a working tic-tac-toe game. And you've just learned the basics of React too. So *you're* probably the real winner here.
## Adding Time Travel
As a final exercise, let's make it possible to "go back in time" to the previous moves in the game.
[View the current code.](https://codepen.io/gaearon/pen/LyyXgK?editors=0010) ### Storing a History of Moves
## Storing a History If we mutated the `squares` array, implementing time travel would be very difficult.
Let's make it possible to revisit old states of the board so we can see what it looked like after any of the previous moves. We're already creating a new `squares` array each time a move is made, which means we can easily store the past board states simultaneously. However, we used `slice()` to create a new copy of the `squares` array after every move, and [treated it as immutable](#why-immutability-is-important). This will allow us to store every past version of the `squares` array, and navigate between the turns that have already happened.
Let's plan to store an object like this in state: 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:
```javascript ```javascript
history = [ history = [
// Before first move
{ {
squares: [ squares: [
null, null, null, null, null, null,
@ -712,6 +745,7 @@ history = [
null, null, null, null, null, null,
] ]
}, },
// After first move
{ {
squares: [ squares: [
null, null, null, null, null, null,
@ -719,13 +753,27 @@ history = [
null, null, 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 be responsible for displaying the list of moves. So just as we pulled the state up before from Square into Board, let's now pull it up again from Board into Game – so that we have all the information we need at the top level. Now we need to decide which component should own the `history` state.
### Lifting State Up, Again
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.
Placing the `history` state into the Game component lets us remove the `squares` state from its child Board component. Just like we ["lifted state up"](#lifting-state-up) from the Square component into the Board component, we are now lifting it up from the Board into the top-level Game component. This gives the Game component full control over what the Board's data, and lets it instruct the Board to render past turns from the `history`.
First, set up the initial state for Game by adding a constructor to it: First, we'll set up the initial state for the Game component within its constructor:
```javascript{2-10} ```javascript{2-10}
class Game extends React.Component { class Game extends React.Component {
@ -755,13 +803,13 @@ class Game extends React.Component {
} }
``` ```
Then change Board so that it takes `squares` via props and has its own `onClick` prop specified by Game, like the transformation we made for Square earlier. You can pass the location of each square into the click handler so that we still know which square was clicked. Here is a list of steps you need to do: Next, we'll have the Board component receive `squares` and `onClick` props from the Game component by props. Since we now have a single click handler in Board for many Squares, we'll need to pass the location of each Square into the `onClick` handler to indicate which Square was clicked. Here are the required steps to transform the Board component:
* Delete the `constructor` in Board. * Delete the `constructor` in Board.
* Replace `this.state.squares[i]` with `this.props.squares[i]` in Board's `renderSquare`. * Replace `this.state.squares[i]` with `this.props.squares[i]` in Board's `renderSquare`.
* Replace `this.handleClick(i)` with `this.props.onClick(i)` in Board's `renderSquare`. * Replace `this.handleClick(i)` with `this.props.onClick(i)` in Board's `renderSquare`.
Now the whole Board component looks like this: The Board component now looks like this:
```javascript{17,18} ```javascript{17,18}
class Board extends React.Component { class Board extends React.Component {
@ -819,7 +867,7 @@ class Board extends React.Component {
} }
``` ```
Game's `render` should look at the most recent history entry and can take over calculating the game status: We'll update the Game component's `render` function to use the most recent history entry to determine and display the game's status:
```javascript{2-11,16-19,22} ```javascript{2-11,16-19,22}
render() { render() {
@ -851,7 +899,7 @@ Game's `render` should look at the most recent history entry and can take over c
} }
``` ```
Since Game is now rendering the status, we can delete `<div className="status">{status}</div>` and the code calculating the status from the Board's `render` function: 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:
```js{1-4} ```js{1-4}
render() { render() {
@ -877,9 +925,7 @@ Since Game is now rendering the status, we can delete `<div className="status">{
} }
``` ```
Next, we need to move the `handleClick` method implementation from Board to Game. You can cut it from the Board class, and paste it into the Game class. Finally, we need to move the `handleClick` method from the Board component to the Game component. We also need to modify `handleClick` because the Game component's state is structured differently. Within the Game's `handleClick` method, we concatenate new history entries onto `history`.
We also need to change it a little, since Game state is structured differently. Game's `handleClick` can push a new entry onto the stack by concatenating the new history entry to make a new history array.
```javascript{2-4,10-12} ```javascript{2-4,10-12}
handleClick(i) { handleClick(i) {
@ -899,13 +945,30 @@ We also need to change it a little, since Game state is structured differently.
} }
``` ```
At this point, Board only needs `renderSquare` and `render`; the state initialization and click handler should both live in Game. >Note
>
>Unlike the array `push()` method you might be more familiar with, the `concat()` method doesn't mutate the original array, so we prefer it.
At this point, the Board component only needs the `renderSquare` and `render` methods. The game's state and the `handleClick` method should be in the Game component.
**[View the current code.](https://codepen.io/gaearon/pen/EmmOqJ?editors=0010)**
[View the current code.](https://codepen.io/gaearon/pen/EmmOqJ?editors=0010) ### Showing the Past Moves
### Showing the Moves 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.
Let's show the previous moves made in the game so far. We learned earlier that React elements are first-class JS objects and we can store them or pass them around. To render multiple items in React, we pass an array of React elements. The most common way to build that array is to map over your array of data. Let's do that in the `render` method of Game: We learned earlier that React elements are first-class JavaScript objects; we can pass them around in our applications. To render multiple items in React, we can use an array of React elements.
In JavaScript, arrays have a [`map()` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) that is commonly used for mapping data to other data, for example:
```js
const numbers = [1, 2, 3];
const doubled = numbers.map(x => x * 2); // [2, 4, 6]
```
Using the `map` method, we can map our history of moves to React elements representing buttons on the screen, and display a list of buttons to "jump" to past moves.
Let's `map` over the `history` in the Game's `render` method:
```javascript{6-15,34} ```javascript{6-15,34}
render() { render() {
@ -948,20 +1011,18 @@ Let's show the previous moves made in the game so far. We learned earlier that R
} }
``` ```
[View the current code.](https://codepen.io/gaearon/pen/EmmGEa?editors=0010) **[View the current code.](https://codepen.io/gaearon/pen/EmmGEa?editors=0010)**
For each step in the history, we create a list item `<li>` with a button `<button>` inside it that has a click handler which we'll implement shortly. With this code, you should see a list of the moves that have been made in the game, along with a warning that says: For each move in the tic-tac-toes's game's history, we create a list item `<li>` which contains a button `<button>`. The button has a `onClick` handler which calls a method called `this.jumpTo()`. We haven't implemented the `jumpTo()` method yet. For now, we should see a list of the moves that have occurred in the game and a warning that says:
> Warning: > Warning:
> Each child in an array or iterator should have a unique "key" prop. Check the render method of "Game". > Each child in an array or iterator should have a unique "key" prop. Check the render method of "Game".
Let's talk about what that warning means. Let's discuss what the above warning means.
### Keys
When you render a list of items, React always stores some info about each item in the list. If you render a component that has state, that state needs to be stored – and regardless of how you implement your components, React stores a reference to the backing native views. ### Picking a Key
When you update that list, React needs to determine what has changed. You could've added, removed, rearranged, or updated items in the list. When we render a list, React stores some information about each rendered list item. When we update a list, React needs to determine what has changed. We could have added, removed, re-arranged, or updated the list's items.
Imagine transitioning from Imagine transitioning from
@ -978,26 +1039,28 @@ to
<li>Alexa: 5 tasks left</li> <li>Alexa: 5 tasks left</li>
``` ```
To a human eye, it looks likely that Alexa and Ben swapped places and Claudia was added – but React is just a computer program and doesn't know what you intended it to do. As a result, React asks you to specify a *key* property on each element in a list, a string to differentiate each component from its siblings. In this case, `alexa`, `ben`, `claudia` might be sensible keys; if the items correspond to objects in a database, the database ID is usually a good choice: From our perspective, our transition swapped Alexa and Ben's ordering and inserted Claudia between Alexa and Ben. However, React is a computer program and does not know what we intended. Because React cannot know our intentions, we need to specify a *key* property for each list item to differentiate each list item from its siblings. The strings `alexa`, `ben`, `claudia` may be used as keys. If we had access to a database, Alexa, Ben, and Claudia's database IDs could be used as keys.
```html ```html
<li key={user.id}>{user.name}: {user.taskCount} tasks left</li> <li key={user.id}>{user.name}: {user.taskCount} tasks left</li>
``` ```
`key` is a special property that's reserved by React (along with `ref`, a more advanced feature). When an element is created, React pulls off the `key` property and stores the key directly on the returned element. Even though it may look like it is part of props, it cannot be referenced with `this.props.key`. React uses the key automatically while deciding which children to update; there is no way for a component to inquire about its own key. `key` is a special and reserved property in React (along with `ref`, a more advanced feature). When an element is created, React extracts the `key` property and stores the key directly on the returned element. Even though `key` may look like it belongs in `props`, `key` cannot be referenced using `this.props.key`. React automatically uses `key` to decide which components to update. A component cannot inquire about its `key`.
When a list is rerendered, React takes each element in the new version and looks for one with a matching key in the previous list. When a key is added to the set, a component is created; when a key is removed, a component is destroyed. Keys tell React about the identity of each component, so that it can maintain the state across rerenders. If you change the key of a component, it will be completely destroyed and recreated with a new state. When a list is re-rendered, React takes each list item's key and searches the previous list's items for a matching key. If the current list has a key that does not exist in the previous list, React creates a component. If the current list is missing a key that exists in the previous list, React destroys a component. Keys tell React about the identity of each component which allows React to maintain state between re-renders. If a component's key changes, the component will be destroyed and re-created with a new state.
**It's strongly recommended that you assign proper keys whenever you build dynamic lists.** If you don't have an appropriate key handy, you may want to consider restructuring your data so that you do. **It's strongly recommended that you assign proper keys whenever you build dynamic lists.** If you don't have an appropriate key, you may want to consider restructuring your data so that you do.
If you don't specify any key, React will warn you and fall back to using the array index as a key – which is not the correct choice if you ever reorder elements in the list or add/remove items anywhere but the bottom of the list. Explicitly passing `key={i}` silences the warning but has the same problem so isn't recommended in most cases. If no key is specified, React will present a warning and use the array index as a key by default. Using the array index as a key is problematic when trying to re-order a list's items or inserting/removing list items. Explicitly passing `key={i}` silences the warning but has the same problems as array indices and is not recommended in most cases.
Component keys don't need to be globally unique, only unique relative to the immediate siblings. Keys do not need to be globally unique. Keys only needs to be unique between components and their siblings.
### Implementing Time Travel ### Implementing Time Travel
For our move list, we already have a unique ID for each step: the number of the move when it happened. In the Game's `render` method, add the key as `<li key={move}>` and the key warning should disappear: In the tic-tac-toe game's history, each past move has a unique ID associated with it: it's the sequential number of the move. The moves are never re-ordered, deleted, or inserted in the middle, so it's safe to use the move index as a key.
In the Game component's `render` method, we can add the key as `<li key={move}>` and React's warning about keys should disappear:
```js{6} ```js{6}
const moves = history.map((step, move) => { const moves = history.map((step, move) => {
@ -1012,9 +1075,9 @@ For our move list, we already have a unique ID for each step: the number of the
}); });
``` ```
[View the current code.](https://codepen.io/gaearon/pen/PmmXRE?editors=0010) **[View the current code.](https://codepen.io/gaearon/pen/PmmXRE?editors=0010)**
Clicking any of the move buttons throws an error because `jumpTo` is undefined. Let's add a new key to Game's state to indicate which step we're currently viewing. Clicking any of the list item's buttons throws an error because the `jumpTo` method is undefined. Before we implement `jumpTo`, we'll add `stepNumber` to the Game component's state to indicate which step we're currently viewing.
First, add `stepNumber: 0` to the initial state in Game's `constructor`: First, add `stepNumber: 0` to the initial state in Game's `constructor`:
@ -1032,9 +1095,7 @@ class Game extends React.Component {
} }
``` ```
Next, we'll define the `jumpTo` method in Game to update that state. We also want to update `xIsNext`. We set `xIsNext` to true if the index of the move number is an even number. Next, we'll define the `jumpTo` method in Game to update that `stepNumber`. We also set `xIsNext` to true if the number that we're changing `stepNumber` to is even:
Add a method called `jumpTo` to the Game class:
```javascript{5-10} ```javascript{5-10}
handleClick(i) { handleClick(i) {
@ -1053,7 +1114,11 @@ Add a method called `jumpTo` to the Game class:
} }
``` ```
Then update `stepNumber` when a new move is made by adding `stepNumber: history.length` to the state update in Game's `handleClick`. We'll also update `handleClick` to be aware of `stepNumber` when reading the current board state so that you can go back in time then click in the board to create a new entry: We will now make a few changes to the Game's `handleClick` method which fires when you click on a square.
The `stepNumber` state we've added reflects the move displayed to the user now. After we make a new move, we need to update `stepNumber` by adding `stepNumber: history.length` as part of the `this.setState` argument. This ensures we don't get stuck showing the same move after a new one has been made.
We will also replace reading `this.state.history` with `this.state.history.slice(0, this.state.stepNumber + 1)`. This ensures that if we "go back in time" and then make a new move from that point, we throw away all the "future" history that would now become incorrect.
```javascript{2,13} ```javascript{2,13}
handleClick(i) { handleClick(i) {
@ -1074,7 +1139,7 @@ Then update `stepNumber` when a new move is made by adding `stepNumber: history.
} }
``` ```
Now you can modify Game's `render` to read from that step in the history: Finally, we will modify the Game component's `render` method from always rendering the last move to rendering the currently selected move according to `stepNumber`:
```javascript{3} ```javascript{3}
render() { render() {
@ -1085,24 +1150,24 @@ Now you can modify Game's `render` to read from that step in the history:
// the rest has not changed // the rest has not changed
``` ```
[View the current code.](https://codepen.io/gaearon/pen/gWWZgR?editors=0010) **[View the current code.](https://codepen.io/gaearon/pen/gWWZgR?editors=0010)**
If you click any move button now, the board should immediately update to show what the game looked like at that time. If we click on any step in the game's history, the tic-tac-toe board should immediately update to show what the board looked like after that step occurred.
### Wrapping Up ### Wrapping Up
Now, you've made a tic-tac-toe game that: Congratulations! You've created a tic-tac-toe game that:
* lets you play tic-tac-toe, * Lets you play tic-tac-toe,
* indicates when one player has won the game, * Indicates when a player has won the game,
* stores the history of moves during the game, * Stores a game's history as a game progresses,
* allows players to jump back in time to see older versions of the game board. * Allows players to review a game's history and see previous versions of a game's board.
Nice work! We hope you now feel like you have a decent grasp on how React works. Nice work! We hope you now feel like you have a decent grasp on how React works.
Check out the final result here: [Final Result](https://codepen.io/gaearon/pen/gWWZgR?editors=0010). Check out the final result here: [Final Result](https://codepen.io/gaearon/pen/gWWZgR?editors=0010).
If you have extra time or want to practice your new skills, here are some ideas for improvements you could make, listed in order of increasing difficulty: If you have extra time or want to practice your new React skills, here are some ideas for improvements that you could make to the tic-tac-toe game which are listed in order of increasing difficulty:
1. Display the location for each move in the format (col, row) in the move history list. 1. Display the location for each move in the format (col, row) in the move history list.
2. Bold the currently selected item in the move list. 2. Bold the currently selected item in the move list.
@ -1111,4 +1176,4 @@ If you have extra time or want to practice your new skills, here are some ideas
5. When someone wins, highlight the three squares that caused the win. 5. When someone wins, highlight the three squares that caused the win.
6. When no one wins, display a message about the result being a draw. 6. When no one wins, display a message about the result being a draw.
Throughout this tutorial, we have touched on a number of React concepts including elements, components, props, and state. For a more in-depth explanation for each of these topics, check out [the rest of the documentation](/docs/hello-world.html). To learn more about defining components, check out the [`React.Component` API reference](/docs/react-component.html). Throughout this tutorial, we touched on React concepts including elements, components, props, and state. For a more detailed explanation of each of these topics, check out [the rest of the documentation](/docs/hello-world.html). To learn more about defining components, check out the [`React.Component` API reference](/docs/react-component.html).

Loading…
Cancel
Save