You will build a small tic-tac-toe game during this tutorial. This tutorial does not assume any existing React knowledge. 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.
Note
This tutorial is designed for people who prefer tolearn by doing and want to quickly try making something tangible. If you prefer learning each concept step by step, start withDescribing the UI.
The tutorial is divided into several sections:
- Setup for the tutorial will give youa starting point to follow the tutorial.
- Overview will teach youthe fundamentals of React: components, props, and state.
- Completing the game will teach youthe most common techniques in React development.
- Adding time travel will give youa deeper insight into the unique strengths of React.
What are you building?
In this tutorial, you’ll build an interactive tic-tac-toe game with React.
You can see what it will look like when you’re finished here:
import{useState}from'react';functionSquare({value,onSquareClick}){return(<buttonclassName="square"onClick={onSquareClick}>{value}</button>);}functionBoard({xIsNext,squares,onPlay}){functionhandleClick(i){if(calculateWinner(squares) ||squares[i]){return;}constnextSquares =squares.slice();if(xIsNext){nextSquares[i] ='X';}else{nextSquares[i] ='O';}onPlay(nextSquares);}constwinner =calculateWinner(squares);letstatus;if(winner){status ='Winner: ' +winner;}else{status ='Next player: ' +(xIsNext ?'X' :'O');}return(<><divclassName="status">{status}</div><divclassName="board-row"><Squarevalue={squares[0]}onSquareClick={()=>handleClick(0)}/><Squarevalue={squares[1]}onSquareClick={()=>handleClick(1)}/><Squarevalue={squares[2]}onSquareClick={()=>handleClick(2)}/></div><divclassName="board-row"><Squarevalue={squares[3]}onSquareClick={()=>handleClick(3)}/><Squarevalue={squares[4]}onSquareClick={()=>handleClick(4)}/><Squarevalue={squares[5]}onSquareClick={()=>handleClick(5)}/></div><divclassName="board-row"><Squarevalue={squares[6]}onSquareClick={()=>handleClick(6)}/><Squarevalue={squares[7]}onSquareClick={()=>handleClick(7)}/><Squarevalue={squares[8]}onSquareClick={()=>handleClick(8)}/></div></>);}exportdefaultfunctionGame(){const[history,setHistory] =useState([Array(9).fill(null)]);const[currentMove,setCurrentMove] =useState(0);constxIsNext =currentMove %2 ===0;constcurrentSquares =history[currentMove];functionhandlePlay(nextSquares){constnextHistory =[...history.slice(0,currentMove +1),nextSquares];setHistory(nextHistory);setCurrentMove(nextHistory.length -1);}functionjumpTo(nextMove){setCurrentMove(nextMove);}constmoves =history.map((squares,move)=>{letdescription;if(move >0){description ='Go to move #' +move;}else{description ='Go to game start';}return(<likey={move}><buttononClick={()=>jumpTo(move)}>{description}</button></li>);});return(<divclassName="game"><divclassName="game-board"><BoardxIsNext={xIsNext}squares={currentSquares}onPlay={handlePlay}/></div><divclassName="game-info"><ol>{moves}</ol></div></div>);}functioncalculateWinner(squares){constlines =[[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(leti =0;i <lines.length;i++){const[a,b,c] =lines[i];if(squares[a] &&squares[a] ===squares[b] &&squares[a] ===squares[c]){returnsquares[a];}}returnnull;}
If the code doesn’t make sense to you yet, 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 above 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. You’ll start with a simpler template in this tutorial. Our next step is to set you up so that you can start building the game.
Setup for the tutorial
In the live code editor below, clickFork in the top-right corner to open the editor in a new tab using the website CodeSandbox. CodeSandbox lets you write code in your browser and preview how your users will see the app you’ve created. The new tab should display an empty square and the starter code for this tutorial.
exportdefaultfunctionSquare(){return<buttonclassName="square">X</button>;}
Note
You can also follow this tutorial using your local development environment. To do this, you need to:
- InstallNode.js
- In the CodeSandbox tab you opened earlier, press the top-left corner button to open the menu, and then chooseDownload Sandbox in that menu to download an archive of the files locally
- Unzip the archive, then open a terminal and
cdto the directory you unzipped - Install the dependencies with
npm install - Run
npm startto start a local server and follow the prompts to view the code running in a browser
If you get stuck, don’t let this stop you! Follow along online instead and try a local setup again later.
Overview
Now that you’re set up, let’s get an overview of React!
Inspecting the starter code
In CodeSandbox you’ll see three main sections:

- TheFiles section with a list of files like
App.js,index.js,styles.cssinsrcfolder and a folder calledpublic - Thecode editor where you’ll see the source code of your selected file
- Thebrowser section where you’ll see how the code you’ve written will be displayed
TheApp.js file should be selected in theFiles section. The contents of that file in thecode editor should be:
exportdefaultfunctionSquare(){
return<buttonclassName="square">X</button>;
}Thebrowser section should be displaying a square with an X in it like this:

Now let’s have a look at the files in the starter code.
App.js
The code inApp.js creates acomponent. In React, a component is a piece of reusable code that represents a part of a user interface. Components are used to render, manage, and update the UI elements in your application. Let’s look at the component line by line to see what’s going on:
exportdefaultfunctionSquare(){
return<buttonclassName="square">X</button>;
}The first line defines a function calledSquare. Theexport JavaScript keyword makes this function accessible outside of this file. Thedefault keyword tells other files using your code that it’s the main function in your file.
exportdefaultfunctionSquare(){
return<buttonclassName="square">X</button>;
}The second line returns a button. Thereturn JavaScript keyword means whatever comes after is returned as a value to the caller of the function.<button> is aJSX element. A JSX element is a combination of JavaScript code and HTML tags that describes what you’d like to display.className="square" is a button property orprop that tells CSS how to style the button.X is the text displayed inside of the button and</button> closes the JSX element to indicate that any following content shouldn’t be placed inside the button.
styles.css
Click on the file labeledstyles.css in theFiles section of CodeSandbox. This file defines the styles for your React app. The first twoCSS selectors (* andbody) define the style of large parts of your app while the.square selector defines the style of any component where theclassName property is set tosquare. In your code, that would match the button from your Square component in theApp.js file.
index.js
Click on the file labeledindex.js in theFiles section of CodeSandbox. You won’t be editing this file during the tutorial but it is the bridge between the component you created in theApp.js file and the web browser.
import{StrictMode}from'react';
import{createRoot}from'react-dom/client';
import'./styles.css';
importAppfrom'./App';Lines 1-5 bring all the necessary pieces together:
- React
- React’s library to talk to web browsers (React DOM)
- the styles for your components
- the component you created in
App.js.
The remainder of the file brings all the pieces together and injects the final product intoindex.html in thepublic folder.
Building the board
Let’s get back toApp.js. This is where you’ll spend the rest of the tutorial.
Currently the board is only a single square, but you need nine! If you just try and copy paste your square to make two squares like this:
exportdefaultfunctionSquare(){
return<buttonclassName="square">X</button><buttonclassName="square">X</button>;
}You’ll get this error:
<>...</>?React components need to return a single JSX element and not multiple adjacent JSX elements like two buttons. To fix this you can useFragments (<> and</>) to wrap multiple adjacent JSX elements like this:
exportdefaultfunctionSquare(){
return(
<>
<buttonclassName="square">X</button>
<buttonclassName="square">X</button>
</>
);
}Now you should see:

Great! Now you just need to copy-paste a few times to add nine squares and…

Oh no! The squares are all in a single line, not in a grid like you need for our board. To fix this you’ll need to group your squares into rows withdivs and add some CSS classes. While you’re at it, you’ll give each square a number to make sure you know where each square is displayed.
In theApp.js file, update theSquare component to look like this:
exportdefaultfunctionSquare(){
return(
<>
<divclassName="board-row">
<buttonclassName="square">1</button>
<buttonclassName="square">2</button>
<buttonclassName="square">3</button>
</div>
<divclassName="board-row">
<buttonclassName="square">4</button>
<buttonclassName="square">5</button>
<buttonclassName="square">6</button>
</div>
<divclassName="board-row">
<buttonclassName="square">7</button>
<buttonclassName="square">8</button>
<buttonclassName="square">9</button>
</div>
</>
);
}The CSS defined instyles.css styles the divs with theclassName ofboard-row. Now that you’ve grouped your components into rows with the styleddivs you have your tic-tac-toe board:

But you now have a problem. Your component namedSquare, really isn’t a square anymore. Let’s fix that by changing the name toBoard:
exportdefaultfunctionBoard(){
//...
}At this point your code should look something like this:
exportdefaultfunctionBoard(){return(<><divclassName="board-row"><buttonclassName="square">1</button><buttonclassName="square">2</button><buttonclassName="square">3</button></div><divclassName="board-row"><buttonclassName="square">4</button><buttonclassName="square">5</button><buttonclassName="square">6</button></div><divclassName="board-row"><buttonclassName="square">7</button><buttonclassName="square">8</button><buttonclassName="square">9</button></div></>);}
Note
Psssst… That’s a lot to type! It’s okay to copy and paste code from this page. However, if you’re up for a little challenge, we recommend only copying code that you’ve manually typed at least once yourself.
Passing data through props
Next, you’ll want to change the value of a square from empty to “X” when the user clicks on the square. With how you’ve built the board so far you would need to copy-paste the code that updates the square nine times (once for each square you have)! Instead of copy-pasting, React’s component architecture allows you to create a reusable component to avoid messy, duplicated code.
First, you are going to copy the line defining your first square (<button className="square">1</button>) from yourBoard component into a newSquare component:
functionSquare(){
return<buttonclassName="square">1</button>;
}
exportdefaultfunctionBoard(){
// ...
}Then you’ll update the Board component to render thatSquare component using JSX syntax:
// ...
exportdefaultfunctionBoard(){
return(
<>
<divclassName="board-row">
<Square/>
<Square/>
<Square/>
</div>
<divclassName="board-row">
<Square/>
<Square/>
<Square/>
</div>
<divclassName="board-row">
<Square/>
<Square/>
<Square/>
</div>
</>
);
}Note how unlike the browserdivs, your own componentsBoard andSquare must start with a capital letter.
Let’s take a look:

Oh no! You lost the numbered squares you had before. Now each square says “1”. To fix this, you will useprops to pass the value each square should have from the parent component (Board) to its child (Square).
Update theSquare component to read thevalue prop that you’ll pass from theBoard:
functionSquare({value}){
return<buttonclassName="square">1</button>;
}function Square({ value }) indicates the Square component can be passed a prop calledvalue.
Now you want to display thatvalue instead of1 inside every square. Try doing it like this:
functionSquare({value}){
return<buttonclassName="square">value</button>;
}Oops, this is not what you wanted:

You wanted to render the JavaScript variable calledvalue from your component, not the word “value”. To “escape into JavaScript” from JSX, you need curly braces. Add curly braces aroundvalue in JSX like so:
functionSquare({value}){
return<buttonclassName="square">{value}</button>;
}For now, you should see an empty board:

This is because theBoard component hasn’t passed thevalue prop to eachSquare component it renders yet. To fix it you’ll add thevalue prop to eachSquare component rendered by theBoard component:
exportdefaultfunctionBoard(){
return(
<>
<divclassName="board-row">
<Squarevalue="1"/>
<Squarevalue="2"/>
<Squarevalue="3"/>
</div>
<divclassName="board-row">
<Squarevalue="4"/>
<Squarevalue="5"/>
<Squarevalue="6"/>
</div>
<divclassName="board-row">
<Squarevalue="7"/>
<Squarevalue="8"/>
<Squarevalue="9"/>
</div>
</>
);
}Now you should see a grid of numbers again:

Your updated code should look like this:
functionSquare({value}){return<buttonclassName="square">{value}</button>;}exportdefaultfunctionBoard(){return(<><divclassName="board-row"><Squarevalue="1"/><Squarevalue="2"/><Squarevalue="3"/></div><divclassName="board-row"><Squarevalue="4"/><Squarevalue="5"/><Squarevalue="6"/></div><divclassName="board-row"><Squarevalue="7"/><Squarevalue="8"/><Squarevalue="9"/></div></>);}
Making an interactive component
Let’s fill theSquare component with anX when you click it. Declare a function calledhandleClick inside of theSquare. Then, addonClick to the props of the button JSX element returned from theSquare:
functionSquare({value}){
functionhandleClick(){
console.log('clicked!');
}
return(
<button
className="square"
onClick={handleClick}
>
{value}
</button>
);
}If you click on a square now, you should see a log saying"clicked!" in theConsole tab at the bottom of theBrowser section in CodeSandbox. Clicking the square more than once will log"clicked!" again. Repeated console logs with the same message will not create more lines in the console. Instead, you will see an incrementing counter next to your first"clicked!" log.
Note
If you are following this tutorial using your local development environment, you need to open your browser’s Console. For example, if you use the Chrome browser, you can view the Console with the keyboard shortcutShift + Ctrl + J (on Windows/Linux) orOption + ⌘ + J (on macOS).
As a next step, you want the Square component to “remember” that it got clicked, and fill it with an “X” mark. To “remember” things, components usestate.
React provides a special function calleduseState that you can call from your component to let it “remember” things. Let’s store the current value of theSquare in state, and change it when theSquare is clicked.
ImportuseState at the top of the file. Remove thevalue prop from theSquare component. Instead, add a new line at the start of theSquare that callsuseState. Have it return a state variable calledvalue:
import{useState}from'react';
functionSquare(){
const[value,setValue] =useState(null);
functionhandleClick(){
//...value stores the value andsetValue is a function that can be used to change the value. Thenull passed touseState is used as the initial value for this state variable, sovalue here starts off equal tonull.
Since theSquare component no longer accepts props anymore, you’ll remove thevalue prop from all nine of the Square components created by the Board component:
// ...
exportdefaultfunctionBoard(){
return(
<>
<divclassName="board-row">
<Square/>
<Square/>
<Square/>
</div>
<divclassName="board-row">
<Square/>
<Square/>
<Square/>
</div>
<divclassName="board-row">
<Square/>
<Square/>
<Square/>
</div>
</>
);
}Now you’ll changeSquare to display an “X” when clicked. Replace theconsole.log("clicked!"); event handler withsetValue('X');. Now yourSquare component looks like this:
functionSquare(){
const[value,setValue] =useState(null);
functionhandleClick(){
setValue('X');
}
return(
<button
className="square"
onClick={handleClick}
>
{value}
</button>
);
}By calling thisset function from anonClick handler, you’re telling React to re-render thatSquare whenever its<button> is clicked. After the update, theSquare’svalue will be'X', so you’ll see the “X” on the game board. Click on any Square, and “X” should show up:

Each Square has its own state: thevalue stored in each Square is completely independent of the others. When you call aset function in a component, React automatically updates the child components inside too.
After you’ve made the above changes, your code will look like this:
import{useState}from'react';functionSquare(){const[value,setValue] =useState(null);functionhandleClick(){setValue('X');}return(<buttonclassName="square"onClick={handleClick}>{value}</button>);}exportdefaultfunctionBoard(){return(<><divclassName="board-row"><Square/><Square/><Square/></div><divclassName="board-row"><Square/><Square/><Square/></div><divclassName="board-row"><Square/><Square/><Square/></div></>);}
React Developer Tools
React DevTools let you check the props and the state of your React components. You can find the React DevTools tab at the bottom of thebrowser section in CodeSandbox:

To inspect a particular component on the screen, use the button in the top left corner of React DevTools:

Note
Completing the game
By this point, you have all the basic building blocks for your tic-tac-toe game. To have a complete game, you now need to alternate placing “X”s and “O”s on the board, and you need a way to determine a winner.
Lifting state up
Currently, eachSquare component maintains a part of the game’s state. To check for a winner in a tic-tac-toe game, theBoard would need to somehow know the state of each of the 9Square components.
How would you approach that? At first, you might guess that theBoard needs to “ask” eachSquare for thatSquare’s state. Although this approach is technically 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 parentBoard component instead of in eachSquare. TheBoard component can tell eachSquare what to display by passing a prop, like you did when you passed a number to each Square.
To collect data from multiple children, or to have two child components communicate with each other, declare the shared state in their parent component instead. The parent component can pass that state back down to the children via props. This keeps the child components in sync with each other and with their parent.
Lifting state into a parent component is common when React components are refactored.
Let’s take this opportunity to try it out. Edit theBoard component so that it declares a state variable namedsquares that defaults to an array of 9 nulls corresponding to the 9 squares:
// ...
exportdefaultfunctionBoard(){
const[squares,setSquares] =useState(Array(9).fill(null));
return(
// ...
);
}Array(9).fill(null) creates an array with nine elements and sets each of them tonull. TheuseState() call around it declares asquares state variable that’s initially set to that array. Each entry in the array corresponds to the value of a square. When you fill the board in later, thesquares array will look like this:
['O',null,'X','X','X','O','O',null,null]Now yourBoard component needs to pass thevalue prop down to eachSquare that it renders:
exportdefaultfunctionBoard(){
const[squares,setSquares] =useState(Array(9).fill(null));
return(
<>
<divclassName="board-row">
<Squarevalue={squares[0]}/>
<Squarevalue={squares[1]}/>
<Squarevalue={squares[2]}/>
</div>
<divclassName="board-row">
<Squarevalue={squares[3]}/>
<Squarevalue={squares[4]}/>
<Squarevalue={squares[5]}/>
</div>
<divclassName="board-row">
<Squarevalue={squares[6]}/>
<Squarevalue={squares[7]}/>
<Squarevalue={squares[8]}/>
</div>
</>
);
}Next, you’ll edit theSquare component to receive thevalue prop from the Board component. This will require removing the Square component’s own stateful tracking ofvalue and the button’sonClick prop:
functionSquare({value}){
return<buttonclassName="square">{value}</button>;
}At this point you should see an empty tic-tac-toe board:

And your code should look like this:
import{useState}from'react';functionSquare({value}){return<buttonclassName="square">{value}</button>;}exportdefaultfunctionBoard(){const[squares,setSquares] =useState(Array(9).fill(null));return(<><divclassName="board-row"><Squarevalue={squares[0]}/><Squarevalue={squares[1]}/><Squarevalue={squares[2]}/></div><divclassName="board-row"><Squarevalue={squares[3]}/><Squarevalue={squares[4]}/><Squarevalue={squares[5]}/></div><divclassName="board-row"><Squarevalue={squares[6]}/><Squarevalue={squares[7]}/><Squarevalue={squares[8]}/></div></>);}
Each Square will now receive avalue prop that will either be'X','O', ornull for empty squares.
Next, you need to change what happens when aSquare is clicked. TheBoard component now maintains which squares are filled. You’ll need to create a way for theSquare to update theBoard’s state. Since state is private to a component that defines it, you cannot update theBoard’s state directly fromSquare.
Instead, you’ll pass down a function from theBoard component to theSquare component, and you’ll haveSquare call that function when a square is clicked. You’ll start with the function that theSquare component will call when it is clicked. You’ll call that functiononSquareClick:
functionSquare({value}){
return(
<buttonclassName="square"onClick={onSquareClick}>
{value}
</button>
);
}Next, you’ll add theonSquareClick function to theSquare component’s props:
functionSquare({value,onSquareClick}){
return(
<buttonclassName="square"onClick={onSquareClick}>
{value}
</button>
);
}Now you’ll connect theonSquareClick prop to a function in theBoard component that you’ll namehandleClick. To connectonSquareClick tohandleClick you’ll pass a function to theonSquareClick prop of the firstSquare component:
exportdefaultfunctionBoard(){
const[squares,setSquares] =useState(Array(9).fill(null));
return(
<>
<divclassName="board-row">
<Squarevalue={squares[0]}onSquareClick={handleClick}/>
//...
);
}Lastly, you will define thehandleClick function inside the Board component to update thesquares array holding your board’s state:
exportdefaultfunctionBoard(){
const[squares,setSquares] =useState(Array(9).fill(null));
functionhandleClick(){
constnextSquares =squares.slice();
nextSquares[0] ="X";
setSquares(nextSquares);
}
return(
// ...
)
}ThehandleClick function creates a copy of thesquares array (nextSquares) with the JavaScriptslice() Array method. Then,handleClick updates thenextSquares array to addX to the first ([0] index) square.
Calling thesetSquares function lets React know the state of the component has changed. This will trigger a re-render of the components that use thesquares state (Board) as well as its child components (theSquare components that make up the board).
Note
JavaScript supportsclosures which means an inner function (e.g.handleClick) has access to variables and functions defined in an outer function (e.g.Board). ThehandleClick function can read thesquares state and call thesetSquares method because they are both defined inside of theBoard function.
Now you can add X’s to the board… but only to the upper left square. YourhandleClick function is hardcoded to update the index for the upper left square (0). Let’s updatehandleClick to be able to update any square. Add an argumenti to thehandleClick function that takes the index of the square to update:
exportdefaultfunctionBoard(){
const[squares,setSquares] =useState(Array(9).fill(null));
functionhandleClick(i){
constnextSquares =squares.slice();
nextSquares[i] ="X";
setSquares(nextSquares);
}
return(
// ...
)
}Next, you will need to pass thati tohandleClick. You could try to set theonSquareClick prop of square to behandleClick(0) directly in the JSX like this, but it won’t work:
<Squarevalue={squares[0]}onSquareClick={handleClick(0)}/>Here is why this doesn’t work. ThehandleClick(0) call will be a part of rendering the board component. BecausehandleClick(0) alters the state of the board component by callingsetSquares, your entire board component will be re-rendered again. But this runshandleClick(0) again, leading to an infinite loop:
Why didn’t this problem happen earlier?
When you were passingonSquareClick={handleClick}, you were passing thehandleClick function down as a prop. You were not calling it! But now you arecalling that function right away—notice the parentheses inhandleClick(0)—and that’s why it runs too early. You don’twant to callhandleClick until the user clicks!
You could fix this by creating a function likehandleFirstSquareClick that callshandleClick(0), a function likehandleSecondSquareClick that callshandleClick(1), and so on. You would pass (rather than call) these functions down as props likeonSquareClick={handleFirstSquareClick}. This would solve the infinite loop.
However, defining nine different functions and giving each of them a name is too verbose. Instead, let’s do this:
exportdefaultfunctionBoard(){
// ...
return(
<>
<divclassName="board-row">
<Squarevalue={squares[0]}onSquareClick={()=>handleClick(0)}/>
// ...
);
}Notice the new() => syntax. Here,() => handleClick(0) is anarrow function, which is a shorter way to define functions. When the square is clicked, the code after the=> “arrow” will run, callinghandleClick(0).
Now you need to update the other eight squares to callhandleClick from the arrow functions you pass. Make sure that the argument for each call of thehandleClick corresponds to the index of the correct square:
exportdefaultfunctionBoard(){
// ...
return(
<>
<divclassName="board-row">
<Squarevalue={squares[0]}onSquareClick={()=>handleClick(0)}/>
<Squarevalue={squares[1]}onSquareClick={()=>handleClick(1)}/>
<Squarevalue={squares[2]}onSquareClick={()=>handleClick(2)}/>
</div>
<divclassName="board-row">
<Squarevalue={squares[3]}onSquareClick={()=>handleClick(3)}/>
<Squarevalue={squares[4]}onSquareClick={()=>handleClick(4)}/>
<Squarevalue={squares[5]}onSquareClick={()=>handleClick(5)}/>
</div>
<divclassName="board-row">
<Squarevalue={squares[6]}onSquareClick={()=>handleClick(6)}/>
<Squarevalue={squares[7]}onSquareClick={()=>handleClick(7)}/>
<Squarevalue={squares[8]}onSquareClick={()=>handleClick(8)}/>
</div>
</>
);
};Now you can again add X’s to any square on the board by clicking on them:

But this time all the state management is handled by theBoard component!
This is what your code should look like:
import{useState}from'react';functionSquare({value,onSquareClick}){return(<buttonclassName="square"onClick={onSquareClick}>{value}</button>);}exportdefaultfunctionBoard(){const[squares,setSquares] =useState(Array(9).fill(null));functionhandleClick(i){constnextSquares =squares.slice();nextSquares[i] ='X';setSquares(nextSquares);}return(<><divclassName="board-row"><Squarevalue={squares[0]}onSquareClick={()=>handleClick(0)}/><Squarevalue={squares[1]}onSquareClick={()=>handleClick(1)}/><Squarevalue={squares[2]}onSquareClick={()=>handleClick(2)}/></div><divclassName="board-row"><Squarevalue={squares[3]}onSquareClick={()=>handleClick(3)}/><Squarevalue={squares[4]}onSquareClick={()=>handleClick(4)}/><Squarevalue={squares[5]}onSquareClick={()=>handleClick(5)}/></div><divclassName="board-row"><Squarevalue={squares[6]}onSquareClick={()=>handleClick(6)}/><Squarevalue={squares[7]}onSquareClick={()=>handleClick(7)}/><Squarevalue={squares[8]}onSquareClick={()=>handleClick(8)}/></div></>);}
Now that your state handling is in theBoard component, the parentBoard component passes props to the childSquare components so that they can be displayed correctly. When clicking on aSquare, the childSquare component now asks the parentBoard component to update the state of the board. When theBoard’s state changes, both theBoard component and every childSquare re-renders automatically. Keeping the state of all squares in theBoard component will allow it to determine the winner in the future.
Let’s recap what happens when a user clicks the top left square on your board to add anX to it:
- Clicking on the upper left square runs the function that the
buttonreceived as itsonClickprop from theSquare. TheSquarecomponent received that function as itsonSquareClickprop from theBoard. TheBoardcomponent defined that function directly in the JSX. It callshandleClickwith an argument of0. handleClickuses the argument (0) to update the first element of thesquaresarray fromnulltoX.- The
squaresstate of theBoardcomponent was updated, so theBoardand all of its children re-render. This causes thevalueprop of theSquarecomponent with index0to change fromnulltoX.
In the end the user sees that the upper left square has changed from empty to having anX after clicking it.
Note
The DOM<button> element’sonClick 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. You could give any name to theSquare’sonSquareClick prop orBoard’shandleClick function, and the code would work the same. In React, it’s conventional to useonSomething names for props which represent events andhandleSomething for the function definitions which handle those events.
Why immutability is important
Note how inhandleClick, you call.slice() to create a copy of thesquares array instead of modifying the existing array. To explain why, we need to discuss immutability and why immutability is important to learn.
There are generally two approaches to changing data. The first approach is tomutate 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. Here is what it would look like if you mutated thesquares array:
constsquares =[null,null,null,null,null,null,null,null,null];
squares[0] ='X';
// Now `squares` is ["X", null, null, null, null, null, null, null, null];And here is what it would look like if you changed data without mutating thesquares array:
constsquares =[null,null,null,null,null,null,null,null,null];
constnextSquares =['X',null,null,null,null,null,null,null,null];
// Now `squares` is unchanged, but `nextSquares` first element is 'X' rather than `null`The result is the same but by not mutating (changing the underlying data) directly, you gain several benefits.
Immutability makes complex features much easier to implement. Later in this tutorial, you will implement a “time travel” feature that lets you review the game’s history and “jump back” to past moves. This functionality isn’t specific to games—an ability to undo and redo certain actions is a common requirement for apps. Avoiding direct data mutation lets you keep previous versions of the data intact, and reuse them later.
There is also another benefit of immutability. By default, all child components re-render automatically when the state of a parent component changes. This includes even the child components that weren’t affected by the change. Although re-rendering is not by itself noticeable to the user (you shouldn’t actively try to avoid it!), you might want to skip re-rendering a part of the tree that clearly wasn’t affected by it for performance reasons. Immutability makes it very cheap for components to compare whether their data has changed or not. You can learn more about how React chooses when to re-render a component inthememo API reference.
Taking turns
It’s now time to fix a major defect in this tic-tac-toe game: the “O”s cannot be marked on the board.
You’ll set the first move to be “X” by default. Let’s keep track of this by adding another piece of state to the Board component:
functionBoard(){
const[xIsNext,setXIsNext] =useState(true);
const[squares,setSquares] =useState(Array(9).fill(null));
// ...
}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. You’ll update theBoard’shandleClick function to flip the value ofxIsNext:
exportdefaultfunctionBoard(){
const[xIsNext,setXIsNext] =useState(true);
const[squares,setSquares] =useState(Array(9).fill(null));
functionhandleClick(i){
constnextSquares =squares.slice();
if(xIsNext){
nextSquares[i] ="X";
}else{
nextSquares[i] ="O";
}
setSquares(nextSquares);
setXIsNext(!xIsNext);
}
return(
//...
);
}Now, as you click on different squares, they will alternate betweenX andO, as they should!
But wait, there’s a problem. Try clicking on the same square multiple times:

TheX is overwritten by anO! While this would add a very interesting twist to the game, we’re going to stick to the original rules for now.
When you mark a square with anX or anO you aren’t first checking to see if the square already has anX orO value. You can fix this byreturning early. You’ll check to see if the square already has anX or anO. If the square is already filled, you willreturn in thehandleClick function early—before it tries to update the board state.
functionhandleClick(i){
if(squares[i]){
return;
}
constnextSquares =squares.slice();
//...
}Now you can only addX’s orO’s to empty squares! Here is what your code should look like at this point:
import{useState}from'react';functionSquare({value,onSquareClick}){return(<buttonclassName="square"onClick={onSquareClick}>{value}</button>);}exportdefaultfunctionBoard(){const[xIsNext,setXIsNext] =useState(true);const[squares,setSquares] =useState(Array(9).fill(null));functionhandleClick(i){if(squares[i]){return;}constnextSquares =squares.slice();if(xIsNext){nextSquares[i] ='X';}else{nextSquares[i] ='O';}setSquares(nextSquares);setXIsNext(!xIsNext);}return(<><divclassName="board-row"><Squarevalue={squares[0]}onSquareClick={()=>handleClick(0)}/><Squarevalue={squares[1]}onSquareClick={()=>handleClick(1)}/><Squarevalue={squares[2]}onSquareClick={()=>handleClick(2)}/></div><divclassName="board-row"><Squarevalue={squares[3]}onSquareClick={()=>handleClick(3)}/><Squarevalue={squares[4]}onSquareClick={()=>handleClick(4)}/><Squarevalue={squares[5]}onSquareClick={()=>handleClick(5)}/></div><divclassName="board-row"><Squarevalue={squares[6]}onSquareClick={()=>handleClick(6)}/><Squarevalue={squares[7]}onSquareClick={()=>handleClick(7)}/><Squarevalue={squares[8]}onSquareClick={()=>handleClick(8)}/></div></>);}
Declaring a winner
Now that the players can take turns, you’ll want to show when the game is won and there are no more turns to make. To do this you’ll add a helper function calledcalculateWinner that takes an array of 9 squares, checks for a winner and returns'X','O', ornull as appropriate. Don’t worry too much about thecalculateWinner function; it’s not specific to React:
exportdefaultfunctionBoard(){
//...
}
functioncalculateWinner(squares){
constlines =[
[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(leti =0;i <lines.length;i++){
const[a,b,c] =lines[i];
if(squares[a] &&squares[a] ===squares[b] &&squares[a] ===squares[c]){
returnsquares[a];
}
}
returnnull;
}Note
It does not matter whether you definecalculateWinner before or after theBoard. Let’s put it at the end so that you don’t have to scroll past it every time you edit your components.
You will callcalculateWinner(squares) in theBoard component’shandleClick function to check if a player has won. You can perform this check at the same time you check if a user has clicked a square that already has anX or anO. We’d like to return early in both cases:
functionhandleClick(i){
if(squares[i] ||calculateWinner(squares)){
return;
}
constnextSquares =squares.slice();
//...
}To let the players know when the game is over, you can display text such as “Winner: X” or “Winner: O”. To do that you’ll add astatus section to theBoard component. The status will display the winner if the game is over and if the game is ongoing you’ll display which player’s turn is next:
exportdefaultfunctionBoard(){
// ...
constwinner =calculateWinner(squares);
letstatus;
if(winner){
status ="Winner: " +winner;
}else{
status ="Next player: " +(xIsNext ?"X" :"O");
}
return(
<>
<divclassName="status">{status}</div>
<divclassName="board-row">
// ...
)
}Congratulations! You now have a working tic-tac-toe game. And you’ve just learned the basics of React too. Soyou are the real winner here. Here is what the code should look like:
import{useState}from'react';functionSquare({value,onSquareClick}){return(<buttonclassName="square"onClick={onSquareClick}>{value}</button>);}exportdefaultfunctionBoard(){const[xIsNext,setXIsNext] =useState(true);const[squares,setSquares] =useState(Array(9).fill(null));functionhandleClick(i){if(calculateWinner(squares) ||squares[i]){return;}constnextSquares =squares.slice();if(xIsNext){nextSquares[i] ='X';}else{nextSquares[i] ='O';}setSquares(nextSquares);setXIsNext(!xIsNext);}constwinner =calculateWinner(squares);letstatus;if(winner){status ='Winner: ' +winner;}else{status ='Next player: ' +(xIsNext ?'X' :'O');}return(<><divclassName="status">{status}</div><divclassName="board-row"><Squarevalue={squares[0]}onSquareClick={()=>handleClick(0)}/><Squarevalue={squares[1]}onSquareClick={()=>handleClick(1)}/><Squarevalue={squares[2]}onSquareClick={()=>handleClick(2)}/></div><divclassName="board-row"><Squarevalue={squares[3]}onSquareClick={()=>handleClick(3)}/><Squarevalue={squares[4]}onSquareClick={()=>handleClick(4)}/><Squarevalue={squares[5]}onSquareClick={()=>handleClick(5)}/></div><divclassName="board-row"><Squarevalue={squares[6]}onSquareClick={()=>handleClick(6)}/><Squarevalue={squares[7]}onSquareClick={()=>handleClick(7)}/><Squarevalue={squares[8]}onSquareClick={()=>handleClick(8)}/></div></>);}functioncalculateWinner(squares){constlines =[[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(leti =0;i <lines.length;i++){const[a,b,c] =lines[i];if(squares[a] &&squares[a] ===squares[b] &&squares[a] ===squares[c]){returnsquares[a];}}returnnull;}
Adding time travel
As a final exercise, let’s make it possible to “go back in time” to the previous moves in the game.
Storing a history of moves
If you mutated thesquares array, implementing time travel would be very difficult.
However, you usedslice() to create a new copy of thesquares array after every move, and treated it as immutable. This will allow you to store every past version of thesquares array, and navigate between the turns that have already happened.
You’ll store the pastsquares arrays in another array calledhistory, which you’ll store as a new state variable. Thehistory array represents all board states, from the first to the last move, and has a shape like this:
[
// Before first move
[null,null,null,null,null,null,null,null,null],
// After first move
[null,null,null,null,'X',null,null,null,null],
// After second move
[null,null,null,null,'X',null,null,null,'O'],
// ...
]Lifting state up, again
You will now write a new top-level component calledGame to display a list of past moves. That’s where you will place thehistory state that contains the entire game history.
Placing thehistory state into theGame component will let you remove thesquares state from its childBoard component. Just like you “lifted state up” from theSquare component into theBoard component, you will now lift it up from theBoard into the top-levelGame component. This gives theGame component full control over theBoard’s data and lets it instruct theBoard to render previous turns from thehistory.
First, add aGame component withexport default. Have it render theBoard component and some markup:
functionBoard(){
// ...
}
exportdefaultfunctionGame(){
return(
<divclassName="game">
<divclassName="game-board">
<Board/>
</div>
<divclassName="game-info">
<ol>{/*TODO*/}</ol>
</div>
</div>
);
}Note that you are removing theexport default keywords before thefunction Board() { declaration and adding them before thefunction Game() { declaration. This tells yourindex.js file to use theGame component as the top-level component instead of yourBoard component. The additionaldivs returned by theGame component are making room for the game information you’ll add to the board later.
Add some state to theGame component to track which player is next and the history of moves:
exportdefaultfunctionGame(){
const[xIsNext,setXIsNext] =useState(true);
const[history,setHistory] =useState([Array(9).fill(null)]);
// ...Notice how[Array(9).fill(null)] is an array with a single item, which itself is an array of 9nulls.
To render the squares for the current move, you’ll want to read the last squares array from thehistory. You don’t needuseState for this—you already have enough information to calculate it during rendering:
exportdefaultfunctionGame(){
const[xIsNext,setXIsNext] =useState(true);
const[history,setHistory] =useState([Array(9).fill(null)]);
constcurrentSquares =history[history.length -1];
// ...Next, create ahandlePlay function inside theGame component that will be called by theBoard component to update the game. PassxIsNext,currentSquares andhandlePlay as props to theBoard component:
exportdefaultfunctionGame(){
const[xIsNext,setXIsNext] =useState(true);
const[history,setHistory] =useState([Array(9).fill(null)]);
constcurrentSquares =history[history.length -1];
functionhandlePlay(nextSquares){
// TODO
}
return(
<divclassName="game">
<divclassName="game-board">
<BoardxIsNext={xIsNext}squares={currentSquares}onPlay={handlePlay}/>
//...
)
}Let’s make theBoard component fully controlled by the props it receives. Change theBoard component to take three props:xIsNext,squares, and a newonPlay function thatBoard can call with the updated squares array when a player makes a move. Next, remove the first two lines of theBoard function that calluseState:
functionBoard({xIsNext,squares,onPlay}){
functionhandleClick(i){
//...
}
// ...
}Now replace thesetSquares andsetXIsNext calls inhandleClick in theBoard component with a single call to your newonPlay function so theGame component can update theBoard when the user clicks a square:
functionBoard({xIsNext,squares,onPlay}){
functionhandleClick(i){
if(calculateWinner(squares) ||squares[i]){
return;
}
constnextSquares =squares.slice();
if(xIsNext){
nextSquares[i] ="X";
}else{
nextSquares[i] ="O";
}
onPlay(nextSquares);
}
//...
}TheBoard component is fully controlled by the props passed to it by theGame component. You need to implement thehandlePlay function in theGame component to get the game working again.
What shouldhandlePlay do when called? Remember that Board used to callsetSquares with an updated array; now it passes the updatedsquares array toonPlay.
ThehandlePlay function needs to updateGame’s state to trigger a re-render, but you don’t have asetSquares function that you can call any more—you’re now using thehistory state variable to store this information. You’ll want to updatehistory by appending the updatedsquares array as a new history entry. You also want to togglexIsNext, just as Board used to do:
exportdefaultfunctionGame(){
//...
functionhandlePlay(nextSquares){
setHistory([...history,nextSquares]);
setXIsNext(!xIsNext);
}
//...
}Here,[...history, nextSquares] creates a new array that contains all the items inhistory, followed bynextSquares. (You can read the...historyspread syntax as “enumerate all the items inhistory”.)
For example, ifhistory is[[null,null,null], ["X",null,null]] andnextSquares is["X",null,"O"], then the new[...history, nextSquares] array will be[[null,null,null], ["X",null,null], ["X",null,"O"]].
At this point, you’ve moved the state to live in theGame component, and the UI should be fully working, just as it was before the refactor. Here is what the code should look like at this point:
import{useState}from'react';functionSquare({value,onSquareClick}){return(<buttonclassName="square"onClick={onSquareClick}>{value}</button>);}functionBoard({xIsNext,squares,onPlay}){functionhandleClick(i){if(calculateWinner(squares) ||squares[i]){return;}constnextSquares =squares.slice();if(xIsNext){nextSquares[i] ='X';}else{nextSquares[i] ='O';}onPlay(nextSquares);}constwinner =calculateWinner(squares);letstatus;if(winner){status ='Winner: ' +winner;}else{status ='Next player: ' +(xIsNext ?'X' :'O');}return(<><divclassName="status">{status}</div><divclassName="board-row"><Squarevalue={squares[0]}onSquareClick={()=>handleClick(0)}/><Squarevalue={squares[1]}onSquareClick={()=>handleClick(1)}/><Squarevalue={squares[2]}onSquareClick={()=>handleClick(2)}/></div><divclassName="board-row"><Squarevalue={squares[3]}onSquareClick={()=>handleClick(3)}/><Squarevalue={squares[4]}onSquareClick={()=>handleClick(4)}/><Squarevalue={squares[5]}onSquareClick={()=>handleClick(5)}/></div><divclassName="board-row"><Squarevalue={squares[6]}onSquareClick={()=>handleClick(6)}/><Squarevalue={squares[7]}onSquareClick={()=>handleClick(7)}/><Squarevalue={squares[8]}onSquareClick={()=>handleClick(8)}/></div></>);}exportdefaultfunctionGame(){const[xIsNext,setXIsNext] =useState(true);const[history,setHistory] =useState([Array(9).fill(null)]);constcurrentSquares =history[history.length -1];functionhandlePlay(nextSquares){setHistory([...history,nextSquares]);setXIsNext(!xIsNext);}return(<divclassName="game"><divclassName="game-board"><BoardxIsNext={xIsNext}squares={currentSquares}onPlay={handlePlay}/></div><divclassName="game-info"><ol>{/*TODO*/}</ol></div></div>);}functioncalculateWinner(squares){constlines =[[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(leti =0;i <lines.length;i++){const[a,b,c] =lines[i];if(squares[a] &&squares[a] ===squares[b] &&squares[a] ===squares[c]){returnsquares[a];}}returnnull;}
Showing the past moves
Since you are recording the tic-tac-toe game’s history, you can now display a list of past moves to the player.
React elements like<button> are regular JavaScript objects; you can pass them around in your application. To render multiple items in React, you can use an array of React elements.
You already have an array ofhistory moves in state, so now you need to transform it to an array of React elements. In JavaScript, to transform one array into another, you can use thearraymap method:
[1,2,3].map((x)=>x *2)// [2, 4, 6]You’ll usemap to transform yourhistory of moves into React elements representing buttons on the screen, and display a list of buttons to “jump” to past moves. Let’smap over thehistory in the Game component:
exportdefaultfunctionGame(){
const[xIsNext,setXIsNext] =useState(true);
const[history,setHistory] =useState([Array(9).fill(null)]);
constcurrentSquares =history[history.length -1];
functionhandlePlay(nextSquares){
setHistory([...history,nextSquares]);
setXIsNext(!xIsNext);
}
functionjumpTo(nextMove){
// TODO
}
constmoves =history.map((squares,move)=>{
letdescription;
if(move >0){
description ='Go to move #' +move;
}else{
description ='Go to game start';
}
return(
<li>
<buttononClick={()=>jumpTo(move)}>{description}</button>
</li>
);
});
return(
<divclassName="game">
<divclassName="game-board">
<BoardxIsNext={xIsNext}squares={currentSquares}onPlay={handlePlay}/>
</div>
<divclassName="game-info">
<ol>{moves}</ol>
</div>
</div>
);
}You can see what your code should look like below. Note that you should see an error in the developer tools console that says:
You’ll fix this error in the next section.
import{useState}from'react';functionSquare({value,onSquareClick}){return(<buttonclassName="square"onClick={onSquareClick}>{value}</button>);}functionBoard({xIsNext,squares,onPlay}){functionhandleClick(i){if(calculateWinner(squares) ||squares[i]){return;}constnextSquares =squares.slice();if(xIsNext){nextSquares[i] ='X';}else{nextSquares[i] ='O';}onPlay(nextSquares);}constwinner =calculateWinner(squares);letstatus;if(winner){status ='Winner: ' +winner;}else{status ='Next player: ' +(xIsNext ?'X' :'O');}return(<><divclassName="status">{status}</div><divclassName="board-row"><Squarevalue={squares[0]}onSquareClick={()=>handleClick(0)}/><Squarevalue={squares[1]}onSquareClick={()=>handleClick(1)}/><Squarevalue={squares[2]}onSquareClick={()=>handleClick(2)}/></div><divclassName="board-row"><Squarevalue={squares[3]}onSquareClick={()=>handleClick(3)}/><Squarevalue={squares[4]}onSquareClick={()=>handleClick(4)}/><Squarevalue={squares[5]}onSquareClick={()=>handleClick(5)}/></div><divclassName="board-row"><Squarevalue={squares[6]}onSquareClick={()=>handleClick(6)}/><Squarevalue={squares[7]}onSquareClick={()=>handleClick(7)}/><Squarevalue={squares[8]}onSquareClick={()=>handleClick(8)}/></div></>);}exportdefaultfunctionGame(){const[xIsNext,setXIsNext] =useState(true);const[history,setHistory] =useState([Array(9).fill(null)]);constcurrentSquares =history[history.length -1];functionhandlePlay(nextSquares){setHistory([...history,nextSquares]);setXIsNext(!xIsNext);}functionjumpTo(nextMove){// TODO}constmoves =history.map((squares,move)=>{letdescription;if(move >0){description ='Go to move #' +move;}else{description ='Go to game start';}return(<li><buttononClick={()=>jumpTo(move)}>{description}</button></li>);});return(<divclassName="game"><divclassName="game-board"><BoardxIsNext={xIsNext}squares={currentSquares}onPlay={handlePlay}/></div><divclassName="game-info"><ol>{moves}</ol></div></div>);}functioncalculateWinner(squares){constlines =[[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(leti =0;i <lines.length;i++){const[a,b,c] =lines[i];if(squares[a] &&squares[a] ===squares[b] &&squares[a] ===squares[c]){returnsquares[a];}}returnnull;}
As you iterate through thehistory array inside the function you passed tomap, thesquares argument goes through each element ofhistory, and themove argument goes through each array index:0,1,2, …. (In most cases, you’d need the actual array elements, but to render a list of moves you will only need indexes.)
For each move in the tic-tac-toe game’s history, you create a list item<li> which contains a button<button>. The button has anonClick handler which calls a function calledjumpTo (that you haven’t implemented yet).
For now, you should see a list of the moves that occurred in the game and an error in the developer tools console. Let’s discuss what the “key” error means.
Picking a key
When you render a list, React stores some information about each rendered list item. When you update a list, React needs to determine what has changed. You could have added, removed, re-arranged, or updated the list’s items.
Imagine transitioning from
<li>Alexa: 7 tasks left</li>
<li>Ben: 5 tasks left</li>to
<li>Ben: 9 tasks left</li>
<li>Claudia: 8 tasks left</li>
<li>Alexa: 5 tasks left</li>In addition to the updated counts, a human reading this would probably say that you swapped Alexa and Ben’s ordering and inserted Claudia between Alexa and Ben. However, React is a computer program and does not know what you intended, so you need to specify akey property for each list item to differentiate each list item from its siblings. If your data was from a database, Alexa, Ben, and Claudia’s database IDs could be used as keys.
<likey={user.id}>
{user.name}:{user.taskCount} tasks left
</li>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 didn’t exist before, React creates a component. If the current list is missing a key that existed in the previous list, React destroys the previous component. If two keys match, the corresponding component is moved.
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.
key is a special and reserved property in React. When an element is created, React extracts thekey property and stores the key directly on the returned element. Even thoughkey may look like it is passed as props, React automatically useskey to decide which components to update. There’s no way for a component to ask whatkey its parent specified.
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 no key is specified, React will report an error 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 passingkey={i} silences the error but has the same problems as array indices and is not recommended in most cases.
Keys do not need to be globally unique; they only need to be unique between components and their siblings.
Implementing time travel
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. Moves will never be re-ordered, deleted, or inserted in the middle, so it’s safe to use the move index as a key.
In theGame function, you can add the key as<li key={move}>, and if you reload the rendered game, React’s “key” error should disappear:
constmoves =history.map((squares,move)=>{
//...
return(
<likey={move}>
<buttononClick={()=>jumpTo(move)}>{description}</button>
</li>
);
});import{useState}from'react';functionSquare({value,onSquareClick}){return(<buttonclassName="square"onClick={onSquareClick}>{value}</button>);}functionBoard({xIsNext,squares,onPlay}){functionhandleClick(i){if(calculateWinner(squares) ||squares[i]){return;}constnextSquares =squares.slice();if(xIsNext){nextSquares[i] ='X';}else{nextSquares[i] ='O';}onPlay(nextSquares);}constwinner =calculateWinner(squares);letstatus;if(winner){status ='Winner: ' +winner;}else{status ='Next player: ' +(xIsNext ?'X' :'O');}return(<><divclassName="status">{status}</div><divclassName="board-row"><Squarevalue={squares[0]}onSquareClick={()=>handleClick(0)}/><Squarevalue={squares[1]}onSquareClick={()=>handleClick(1)}/><Squarevalue={squares[2]}onSquareClick={()=>handleClick(2)}/></div><divclassName="board-row"><Squarevalue={squares[3]}onSquareClick={()=>handleClick(3)}/><Squarevalue={squares[4]}onSquareClick={()=>handleClick(4)}/><Squarevalue={squares[5]}onSquareClick={()=>handleClick(5)}/></div><divclassName="board-row"><Squarevalue={squares[6]}onSquareClick={()=>handleClick(6)}/><Squarevalue={squares[7]}onSquareClick={()=>handleClick(7)}/><Squarevalue={squares[8]}onSquareClick={()=>handleClick(8)}/></div></>);}exportdefaultfunctionGame(){const[xIsNext,setXIsNext] =useState(true);const[history,setHistory] =useState([Array(9).fill(null)]);constcurrentSquares =history[history.length -1];functionhandlePlay(nextSquares){setHistory([...history,nextSquares]);setXIsNext(!xIsNext);}functionjumpTo(nextMove){// TODO}constmoves =history.map((squares,move)=>{letdescription;if(move >0){description ='Go to move #' +move;}else{description ='Go to game start';}return(<likey={move}><buttononClick={()=>jumpTo(move)}>{description}</button></li>);});return(<divclassName="game"><divclassName="game-board"><BoardxIsNext={xIsNext}squares={currentSquares}onPlay={handlePlay}/></div><divclassName="game-info"><ol>{moves}</ol></div></div>);}functioncalculateWinner(squares){constlines =[[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(leti =0;i <lines.length;i++){const[a,b,c] =lines[i];if(squares[a] &&squares[a] ===squares[b] &&squares[a] ===squares[c]){returnsquares[a];}}returnnull;}
Before you can implementjumpTo, you need theGame component to keep track of which step the user is currently viewing. To do this, define a new state variable calledcurrentMove, defaulting to0:
exportdefaultfunctionGame(){
const[xIsNext,setXIsNext] =useState(true);
const[history,setHistory] =useState([Array(9).fill(null)]);
const[currentMove,setCurrentMove] =useState(0);
constcurrentSquares =history[history.length -1];
//...
}Next, update thejumpTo function insideGame to update thatcurrentMove. You’ll also setxIsNext totrue if the number that you’re changingcurrentMove to is even.
exportdefaultfunctionGame(){
// ...
functionjumpTo(nextMove){
setCurrentMove(nextMove);
setXIsNext(nextMove %2 ===0);
}
//...
}You will now make two changes to theGame’shandlePlay function which is called when you click on a square.
- If you “go back in time” and then make a new move from that point, you only want to keep the history up to that point. Instead of adding
nextSquaresafter all items (...spread syntax) inhistory, you’ll add it after all items inhistory.slice(0, currentMove + 1)so that you’re only keeping that portion of the old history. - Each time a move is made, you need to update
currentMoveto point to the latest history entry.
functionhandlePlay(nextSquares){
constnextHistory =[...history.slice(0,currentMove +1),nextSquares];
setHistory(nextHistory);
setCurrentMove(nextHistory.length -1);
setXIsNext(!xIsNext);
}Finally, you will modify theGame component to render the currently selected move, instead of always rendering the final move:
exportdefaultfunctionGame(){
const[xIsNext,setXIsNext] =useState(true);
const[history,setHistory] =useState([Array(9).fill(null)]);
const[currentMove,setCurrentMove] =useState(0);
constcurrentSquares =history[currentMove];
// ...
}If you 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.
import{useState}from'react';functionSquare({value,onSquareClick}){return(<buttonclassName="square"onClick={onSquareClick}>{value}</button>);}functionBoard({xIsNext,squares,onPlay}){functionhandleClick(i){if(calculateWinner(squares) ||squares[i]){return;}constnextSquares =squares.slice();if(xIsNext){nextSquares[i] ='X';}else{nextSquares[i] ='O';}onPlay(nextSquares);}constwinner =calculateWinner(squares);letstatus;if(winner){status ='Winner: ' +winner;}else{status ='Next player: ' +(xIsNext ?'X' :'O');}return(<><divclassName="status">{status}</div><divclassName="board-row"><Squarevalue={squares[0]}onSquareClick={()=>handleClick(0)}/><Squarevalue={squares[1]}onSquareClick={()=>handleClick(1)}/><Squarevalue={squares[2]}onSquareClick={()=>handleClick(2)}/></div><divclassName="board-row"><Squarevalue={squares[3]}onSquareClick={()=>handleClick(3)}/><Squarevalue={squares[4]}onSquareClick={()=>handleClick(4)}/><Squarevalue={squares[5]}onSquareClick={()=>handleClick(5)}/></div><divclassName="board-row"><Squarevalue={squares[6]}onSquareClick={()=>handleClick(6)}/><Squarevalue={squares[7]}onSquareClick={()=>handleClick(7)}/><Squarevalue={squares[8]}onSquareClick={()=>handleClick(8)}/></div></>);}exportdefaultfunctionGame(){const[xIsNext,setXIsNext] =useState(true);const[history,setHistory] =useState([Array(9).fill(null)]);const[currentMove,setCurrentMove] =useState(0);constcurrentSquares =history[currentMove];functionhandlePlay(nextSquares){constnextHistory =[...history.slice(0,currentMove +1),nextSquares];setHistory(nextHistory);setCurrentMove(nextHistory.length -1);setXIsNext(!xIsNext);}functionjumpTo(nextMove){setCurrentMove(nextMove);setXIsNext(nextMove %2 ===0);}constmoves =history.map((squares,move)=>{letdescription;if(move >0){description ='Go to move #' +move;}else{description ='Go to game start';}return(<likey={move}><buttononClick={()=>jumpTo(move)}>{description}</button></li>);});return(<divclassName="game"><divclassName="game-board"><BoardxIsNext={xIsNext}squares={currentSquares}onPlay={handlePlay}/></div><divclassName="game-info"><ol>{moves}</ol></div></div>);}functioncalculateWinner(squares){constlines =[[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(leti =0;i <lines.length;i++){const[a,b,c] =lines[i];if(squares[a] &&squares[a] ===squares[b] &&squares[a] ===squares[c]){returnsquares[a];}}returnnull;}
Final cleanup
If you look at the code very closely, you may notice thatxIsNext === true whencurrentMove is even andxIsNext === false whencurrentMove is odd. In other words, if you know the value ofcurrentMove, then you can always figure out whatxIsNext should be.
There’s no reason for you to store both of these in state. In fact, always try to avoid redundant state. Simplifying what you store in state reduces bugs and makes your code easier to understand. ChangeGame so that it doesn’t storexIsNext as a separate state variable and instead figures it out based on thecurrentMove:
exportdefaultfunctionGame(){
const[history,setHistory] =useState([Array(9).fill(null)]);
const[currentMove,setCurrentMove] =useState(0);
constxIsNext =currentMove %2 ===0;
constcurrentSquares =history[currentMove];
functionhandlePlay(nextSquares){
constnextHistory =[...history.slice(0,currentMove +1),nextSquares];
setHistory(nextHistory);
setCurrentMove(nextHistory.length -1);
}
functionjumpTo(nextMove){
setCurrentMove(nextMove);
}
// ...
}You no longer need thexIsNext state declaration or the calls tosetXIsNext. Now, there’s no chance forxIsNext to get out of sync withcurrentMove, even if you make a mistake while coding the components.
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:
import{useState}from'react';functionSquare({value,onSquareClick}){return(<buttonclassName="square"onClick={onSquareClick}>{value}</button>);}functionBoard({xIsNext,squares,onPlay}){functionhandleClick(i){if(calculateWinner(squares) ||squares[i]){return;}constnextSquares =squares.slice();if(xIsNext){nextSquares[i] ='X';}else{nextSquares[i] ='O';}onPlay(nextSquares);}constwinner =calculateWinner(squares);letstatus;if(winner){status ='Winner: ' +winner;}else{status ='Next player: ' +(xIsNext ?'X' :'O');}return(<><divclassName="status">{status}</div><divclassName="board-row"><Squarevalue={squares[0]}onSquareClick={()=>handleClick(0)}/><Squarevalue={squares[1]}onSquareClick={()=>handleClick(1)}/><Squarevalue={squares[2]}onSquareClick={()=>handleClick(2)}/></div><divclassName="board-row"><Squarevalue={squares[3]}onSquareClick={()=>handleClick(3)}/><Squarevalue={squares[4]}onSquareClick={()=>handleClick(4)}/><Squarevalue={squares[5]}onSquareClick={()=>handleClick(5)}/></div><divclassName="board-row"><Squarevalue={squares[6]}onSquareClick={()=>handleClick(6)}/><Squarevalue={squares[7]}onSquareClick={()=>handleClick(7)}/><Squarevalue={squares[8]}onSquareClick={()=>handleClick(8)}/></div></>);}exportdefaultfunctionGame(){const[history,setHistory] =useState([Array(9).fill(null)]);const[currentMove,setCurrentMove] =useState(0);constxIsNext =currentMove %2 ===0;constcurrentSquares =history[currentMove];functionhandlePlay(nextSquares){constnextHistory =[...history.slice(0,currentMove +1),nextSquares];setHistory(nextHistory);setCurrentMove(nextHistory.length -1);}functionjumpTo(nextMove){setCurrentMove(nextMove);}constmoves =history.map((squares,move)=>{letdescription;if(move >0){description ='Go to move #' +move;}else{description ='Go to game start';}return(<likey={move}><buttononClick={()=>jumpTo(move)}>{description}</button></li>);});return(<divclassName="game"><divclassName="game-board"><BoardxIsNext={xIsNext}squares={currentSquares}onPlay={handlePlay}/></div><divclassName="game-info"><ol>{moves}</ol></div></div>);}functioncalculateWinner(squares){constlines =[[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(leti =0;i <lines.length;i++){const[a,b,c] =lines[i];if(squares[a] &&squares[a] ===squares[b] &&squares[a] ===squares[c]){returnsquares[a];}}returnnull;}
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:
- For the current move only, show “You are at move #…” instead of a button.
- Rewrite
Boardto use two loops to make the squares instead of hardcoding them. - Add a toggle button that lets you sort the moves in either ascending or descending order.
- 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).
- Display the location for each move in the format (row, col) in the move history list.
Throughout this tutorial, you’ve touched on React concepts including elements, components, props, and state. Now that you’ve seen how these concepts work when building a game, check outThinking in React to see how the same React concepts work when building an app’s UI.