diff --git a/beta/public/images/tutorial/devtools.png b/beta/public/images/tutorial/devtools.png index 6c47657030c..b099e211329 100644 Binary files a/beta/public/images/tutorial/devtools.png and b/beta/public/images/tutorial/devtools.png differ diff --git a/beta/src/content/learn/tic-tac-toe-tutorial.md b/beta/src/content/learn/tic-tac-toe-tutorial.md new file mode 100644 index 00000000000..ac6d38e6bb0 --- /dev/null +++ b/beta/src/content/learn/tic-tac-toe-tutorial.md @@ -0,0 +1,1102 @@ +--- +title: 'Tutorial: Tic-Tac-Toe' +--- + +We'll explain things as we go, but we suggest you first read through the [Quick Start](/learn) page to get an overview of the main concepts in React. + +## Before We Start the Tutorial {/*before-we-start-the-tutorial*/} + +We will build a small game during this tutorial. **You might be tempted to skip it because you're not building games -- but give it a chance.** The techniques you'll learn in the tutorial are fundamental to building any React app, and fully understanding it will give you a deep understanding of React. + + + +This tutorial is designed for people who prefer to **learn by doing**. If you prefer learning concepts from the ground up, check out our [step-by-step guide](/learn/describing-the-ui). You might find this tutorial and the guide complementary to each other. + + + +The tutorial is divided into several sections: + +- [Setup for the Tutorial](#setup-for-the-tutorial) will give you **a starting point** to follow the tutorial. +- [Overview](#overview) will teach you **the fundamentals** of React: components, props, and state. +- [Completing the Game](#completing-the-game) will teach you **the most common techniques** in React development. +- [Adding Time Travel](#adding-time-travel) will give you **a deeper insight** into the unique strengths of React. + +You don't have to complete all of the sections at once to get the value out of this tutorial. Try to get as far as you can -- even if it's one or two sections. + +### What Are We Building? {/*what-are-we-building*/} + +In this tutorial, we'll show how to build an interactive tic-tac-toe game with React. + +You can see what we'll be building [here](#wrapping-up). 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. + +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 it is updated as the game progresses. + +Once you've played around with the finished tic-tac-toe game, keep scrolling. 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 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 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. + +We'll also assume that you've at least skimmed through the [Quick Start](/learn) to get a feel for the main concepts in React. + +## Setup for the Tutorial {/*setup-for-the-tutorial*/} + +In the live code editor below, click **Fork** in the top-right corner to open the editor in a new tab using the website CodeSandbox. The new tab should display an empty tic-tac-toe game board and starter code for this tutorial. + + + +```jsx App.js +function Square() { + return ; +} + +function Board() { + function renderSquare(i) { + return ; + } + + const status = 'Next player: X'; + + return ( +
+
{status}
+
+ {renderSquare(0)} + {renderSquare(1)} + {renderSquare(2)} +
+
+ {renderSquare(3)} + {renderSquare(4)} + {renderSquare(5)} +
+
+ {renderSquare(6)} + {renderSquare(7)} + {renderSquare(8)} +
+
+ ); +} + +export default function Game() { + return ( +
+
+ +
+
+
    {/* TODO */}
+
+
+ ); +} +``` + +```jsx utils.js +// Don't worry too much about this code; it's not specific to React + +export 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; +} +``` + +```css +body { + font: 14px 'Century Gothic', Futura, sans-serif; + margin: 20px; +} + +ol, +ul { + padding-left: 30px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.square:focus { + outline: none; +} + +.kbd-navigation .square:focus { + background: #ddd; +} + +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +
+ +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. + +### Setup Option 1: Write Code in the Browser {/*setup-option-1-write-code-in-the-browser*/} + +This is the quickest way to get started! + +You can use the new browser tab you opened and edit the code there, without installing anything. (You could also edit the code inline on this page, but it'll be easier to have it open in a separate tab so you can switch back and forth easily.) + +To continue with this approach, skip the second setup option, and go to the [Overview](#overview) section to get an overview of React. + +### Setup Option 2: Local Development Environment {/*setup-option-2-local-development-environment*/} + +If you'd prefer to use your own code editor (eg: [Visual Studio Code](https://code.visualstudio.com/)), you can download the files for this tutorial to your computer. + +Here are the steps to follow: + +1. Make sure you have a recent version of [Node.js](https://nodejs.org/en/) installed. +2. In the CodeSandbox tab you opened by clicking Fork, press the menu button in the top left corner of the page, then choose **File > Export to ZIP**. +3. Unzip the archive, then open a terminal and `cd` to the directory unzipped. +4. The files you downloaded use a tool called [Create React App](https://create-react-app.dev/). To install the dependencies needed to run it, run `npm install`. + +Now if you run `npm start` in that folder, it should run a local server, open `http://localhost:3000/` in the browser, and show you the empty tic-tac-toe board. + +### Help, I'm Stuck! {/*help-im-stuck*/} + +If you get stuck, check out the [community support resources](/community/support.html). In particular, [Reactiflux Chat](https://discord.gg/reactiflux) is a great way to get help quickly. + +## Overview {/*overview*/} + +Now that you're set up, let's get an overview of React! + +### Inspecting the Starter Code {/*inspecting-the-starter-code*/} + +Open the `App.js` file to see the starter code. (If you downloaded the files, you'll find it in the `src` directory.) + +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 and programming the tic-tac-toe game. + +By inspecting the code, you'll notice that we have three React components: + +- Square +- Board +- Game + +The Square component renders a single `; +} +``` + +Parameters passed to a component are called **props**; when React calls your component, it passes the props as a single object. + +Before: + +![Empty Tic-Tac-Toe board](../images/tutorial/tictac-empty.png) + +After: You should see a number in each square in the rendered output. + +![Tic-Tac-Toe board with sequentially numbered squares](../images/tutorial/tictac-numbers.png) + +**[View the full code at this point](https://codesandbox.io/s/react-tutorial-tic-tac-toe-passing-data-through-props-189030?file=/App.js)** + +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. + +### Making an Interactive Component {/*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 to add `onClick` to its props: + +```jsx {3} +function Square({value}) { + return ( + + ); +} +``` + +React will call this function when the HTML element is clicked. If you click on a Square now, you should see `click` in your browser's devtools console. To see the console when developing in your browser, find "Console" tab below the "Browser" section. Clicking a square more than once will increment a counter next to the text `click`, indicating how many times you've clicked a square. + +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**. + +React provides a special function called `useState` that you can call from your component to let it "remember" things. Let's store the current value of the Square in state, and change it when the Square is clicked. + +Import `useState` at the top of the file, then remove the `{value}` props and replace it with a call to `useState` that defines a state variable called `value`: + +```jsx {1,3,4} +import {useState} from 'react'; + +function Square() { + const [value, setValue] = useState(null); + return ( + // ... +``` + +The variables `value` and `setValue` can be named anything you want, but it's conventional to use [array destructuring](/learn/a-javascript-refresher#array-destructuring) to name them like `[something, setSomething]`. The `null` value we pass to `useState` is used as the initial value for this state variable, so `value` here starts off equal to `null`. + +Now we'll change Square to display an "X" when clicked. Replace the `onClick={...}` event handler with `onClick={() => setValue('X')}`. Now our Square component looks like this: + +```jsx {4} +function Square() { + const [value, setValue] = useState(null); + return ( + + ); +} +``` + +By calling this `set` function from an `onClick` handler, we tell React to re-render that Square whenever its ` + ); +} +``` + +When a Square is clicked, the `handleSquareClick` 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 ` + + ); + }); + + return ( +
+
+ +
+
+
    {moves}
+
+
+ ); +} +``` + +**[View the full code at this point](https://codesandbox.io/s/react-tutorial-tic-tac-toe-showing-the-past-moves-rpstlr?file=/App.js)** + +As we iterate through `history` array, the `step` variable goes through each element of `history`, and `index` goes through each array index: 0, 1, 2, …. (In most cases, you'd need the actual array elements, but in this case we don't use `step`.) + +For each move in the tic-tac-toe game's history, we create a list item `
  • ` which contains a button ` +
  • + ); +}); +``` + +**[View the full code at this point](https://codesandbox.io/s/react-tutorial-tic-tac-toe-implementing-time-travel-kddrmu?file=/App.js)** + +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, define it as a new state variable, defaulting to `0`: + +```jsx {1,2,3} +export default function Game() { + const [xIsNext, setXIsNext] = useState(true); + const [history, setHistory] = useState([Array(9).fill(null)]); + const [stepNumber, setStepNumber] = useState(0); + const currentSquares = history[history.length - 1]; +``` + +Next, add a `jumpTo` function inside Game to update that `stepNumber`. We'll also set `xIsNext` to true if the number that we're changing `stepNumber` to is even: + +```jsx {5-10} +export default function Game() { + // ... + + function jumpTo(step) { + setStepNumber(step); + setXIsNext(step % 2 === 0); + } +``` + +We will now make two changes to the Game's `handlePlay` method which fires when you click on a square. + +- If we "go back in time" and then make a new move from that point, we only want to keep the history up to that point, so we'll call `history.slice(0, stepNumber + 1)` before `.concat()` to make sure we're only keeping that portion of the old history. +- Each time a move is made, we need to update `stepNumber` to point to the latest history entry. + +```jsx {2-4} +function handlePlay(newSquares) { + let newHistory = history.slice(0, stepNumber + 1).concat([newSquares]); + setHistory(newHistory); + setStepNumber(newHistory.length - 1); + setXIsNext(!xIsNext); +} +``` + +Finally, we will modify the Game component to render the currently selected move, instead of always rendering the final move: + +```jsx {5} +export default function Game() { + const [xIsNext, setXIsNext] = useState(true); + const [history, setHistory] = useState([Array(9).fill(null)]); + const [stepNumber, setStepNumber] = useState(0); + const currentSquares = history[stepNumber]; + + // ... +``` + +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. + +**[View the full code at this point](https://codesandbox.io/s/react-tutorial-tic-tac-toe-implementing-time-travel-part-2-zx6nw1?file=/App.js)** + +### Final Cleanup {/*final-cleanup*/} + +If you're eagle-eyed, you may notice that `xIsNext === true` when `stepNumber` is even and `xIsNext === false` when `stepNumber` is odd. In other words, if we know the value of `stepNumber`, then we can always figure out what `xIsNext` should be. + +There's no reason for us to store both of these in state. It's a best practice to avoid redundant pieces of state, because simplifying what you store in state helps reduce bugs and make your code easier to understand. Let's change Game so that it no longer stores `xIsNext` as a separate state variable and instead figures it out based on the current value of `stepNumber`: + +```jsx {4,10,14} +export default function Game() { + const [history, setHistory] = useState([Array(9).fill(null)]); + const [stepNumber, setStepNumber] = useState(0); + const xIsNext = stepNumber % 2 === 0; + const currentSquares = history[stepNumber]; + + function handlePlay(newSquares) { + let newHistory = history.slice(0, stepNumber + 1).concat([newSquares]); + setHistory(newHistory); + setStepNumber(newHistory.length - 1); + } + + function jumpTo(step) { + setStepNumber(step); + } + + // ... +``` + +We no longer need the `xIsNext` state declaration or the calls to `setXIsNext`. Now, there's no chance for `xIsNext` to get out of sync with `stepNumber`, even if we make an error while coding the components. + +### Wrapping Up {/*wrapping-up*/} + +Congratulations! You've created a tic-tac-toe game that: + +- Lets you play tic-tac-toe, +- Indicates when a player has won the game, +- Stores a game's history as a game progresses, +- 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 of how React works. + +Check out the final result here: + + + +```jsx App.js +import {useState} from 'react'; +import {calculateWinner} from './utils.js'; + +function Square({value, onClick}) { + return ( + + ); +} + +function Board({xIsNext, squares, onPlay}) { + function handleClick(i) { + if (calculateWinner(squares) || squares[i]) { + return; + } + const newSquares = squares.slice(); + newSquares[i] = xIsNext ? 'X' : 'O'; + onPlay(newSquares); + } + + function renderSquare(i) { + return handleClick(i)} />; + } + + const winner = calculateWinner(squares); + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (xIsNext ? 'X' : 'O'); + } + + return ( +
    +
    {status}
    +
    + {renderSquare(0)} + {renderSquare(1)} + {renderSquare(2)} +
    +
    + {renderSquare(3)} + {renderSquare(4)} + {renderSquare(5)} +
    +
    + {renderSquare(6)} + {renderSquare(7)} + {renderSquare(8)} +
    +
    + ); +} + +export default function Game() { + const [history, setHistory] = useState([Array(9).fill(null)]); + const [stepNumber, setStepNumber] = useState(0); + const xIsNext = stepNumber % 2 === 0; + const currentSquares = history[stepNumber]; + + function handlePlay(newSquares) { + let newHistory = history.slice(0, stepNumber + 1).concat([newSquares]); + setHistory(newHistory); + setStepNumber(newHistory.length - 1); + } + + function jumpTo(step) { + setStepNumber(step); + } + + const moves = history.map((step, move) => { + const description = move > 0 ? `Go to move #${move}` : 'Go to game start'; + return ( +
  • + +
  • + ); + }); + + return ( +
    +
    + +
    +
    +
      {moves}
    +
    +
    + ); +} +``` + +```jsx utils.js +// Don't worry too much about this code; it's not specific to React + +export 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; +} +``` + +```css +body { + font: 14px 'Century Gothic', Futura, sans-serif; + margin: 20px; +} + +ol, +ul { + padding-left: 30px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.square:focus { + outline: none; +} + +.kbd-navigation .square:focus { + background: #ddd; +} + +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} +``` + +
    + +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, listed in order of increasing difficulty: + +1. Rewrite Board to use two loops to make the squares instead of hardcoding them. +1. Add a toggle button that lets you sort the moves in either ascending or descending order. +1. When someone wins, highlight the three squares that caused the win (and when no one wins, display a message about the result being a draw). +1. Display the location for each move in the format (col, row) in the move history list. + +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. diff --git a/beta/src/sidebarLearn.json b/beta/src/sidebarLearn.json index d64bcb5581e..8feea0687e4 100644 --- a/beta/src/sidebarLearn.json +++ b/beta/src/sidebarLearn.json @@ -32,10 +32,16 @@ { "title": "Quick Start", "path": "/learn", - "routes": [{ - "title": "Thinking in React", - "path": "/learn/thinking-in-react" - }] + "routes": [ + { + "title": "Tutorial: Tic-Tac-Toe", + "path": "/learn/tic-tac-toe-tutorial" + }, + { + "title": "Thinking in React", + "path": "/learn/thinking-in-react" + } + ] }, { "title": "Describing the UI", @@ -114,9 +120,7 @@ { "title": "Managing State", "path": "/learn/managing-state", - "tags": [ - "intermediate" - ], + "tags": ["intermediate"], "routes": [ { "title": "Reacting to Input with State", @@ -151,9 +155,7 @@ { "title": "Escape Hatches", "path": "/learn/escape-hatches", - "tags": [ - "advanced" - ], + "tags": ["advanced"], "routes": [ { "title": "Referencing Values with Refs",