Component Breakdown & State Management ā - Building a tic-tac-toe game with React from scratch
Syed Ali Mansoor
Posted on July 4, 2022
Welcome! š
ā¹ļø This post is part of a series, where I pen down my journey as I plan and build a tic-tac-toe game from ideation to release
In the previous post, we designed the app's UI, from displaying the start screen to displaying the result of a tic-tac-toe game:
The React mindset dictates that user interfaces be broken down into individual components, each of whom performs a single task. This will help us abstract away irrelevant logic while developing the app and reuse code whenever possible. Let's understand this with an example:
In this screen, we can see some repetition happening in the form of a piece of UI that displays a player's name and mark (inside the dotted square).
We can either write code to display both player's details separately, or we can create a single component that accepts a player's details as parameters, and then handles displaying those details itself. We can then use that component twice, telling it to display a different player each time:
Hence we only need to write the code to display details once and can reuse it for both players!
Seeing the clear benefit of this approach, let us go ahead and break our app's UI down into individual components. We will start off with the three screens:
- Start
- Settings
- Game
All other components would be children of these, and each of these represents a different section in the user flow. Therefore we could also call them page/layout components.
1. Start
The Start
page will consist of only two components: StartButton
, and Attribution
. Displaying the X and O in the background can be handled by Start
itself.
2. Settings
The SettingsForm
component will house and lay out the SelectGameMode
, SelectGridSize
, and EnterPlayerNames
components, each of which will enable the user to edit the game settings. EnterPlayerNames
will house 2 instances of PlayerNameField
, which will allow editing the name of a single player.
When the game mode is PvP, both fields will be editable, whereas in PvC, only the player 1 field will be editable and the player 2 field will contain the uneditable text "A.I."
3. Game
The top section of the game screen will be contained in GameHeader
and the rest of the components will be direct children of Game
.
Grid
will render a square grid of GridCell
components, and each GridCell
will update its background color and image appropriately when it is clicked or when there is a match.
When the game ends, the Result
component will be displayed, stating the outcome, and RestartButton
will become highlighted, to probe the player(s) to play another game.
Let us now think about the data that our game will need to keep track of.
Defining data
React applications work with data that may frequently change and the UI needs to update in respond to these changes. Such data is called state, and it can be stored either in the global scope, where it is accessible to all components, or in the component scope, where it is accessible to only a single component and optionally its children. State management libraries like Redux allow us to store data in the global scope and write methods to access and change it.
It is good practice to store data that is related to the business logic in the global scope, and that which is related to UI/component logic in the component scope.
We can understand this with the example of an online shopping site. The items in your cart would be stored in the global scope, but whether the cart is currently open/visible or closed/hidden would be stored in the component representing the cart itself.
Using this convention, we can extract the following data to be stored in the global scope of our application:
- Game mode: string containing either "PvC" or "PvP"
type GameMode = "PvC" | "PvP"
- Grid size: the number 3, 4, or 5
type GridSize = 3 | 4 | 5
- Players: array containing 2 player objects, where each player object stores the type of player ("human"/"ai"), the name of player, and their score
type Players = [Player, Player]
type Player = {
type: "human" | "ai"
name: string
score: number
}
- Grid: array containing child arrays, each of which contains cell objects, where each cell object stores its mark (""/"X"/"O") and whether it is part of a match
type Grid = Cell[][]
type Cell = {
mark: "" | "X" | "O"
match: boolean
}
- Current player: number, either 0 or 1, representing the index of the current player
type CurrentPlayer = 0 | 1
- Game end: object representing whether a match has been made, whether there has been a draw and whether the game has ended
type GameEnd = {
match: boolean
draw: boolean
end: boolean
}
We now have ourselves the following global state tree:
{
gameMode,
gridSize,
players,
grid,
currentPlayer,
gameEnd
}
Now that we have a solid component hierarchy, as well as a global state tree, we can finally start the development process.
ā” Join me in the next post in this series, where we will set up our development environment and build the first two pages of the app
ā¤ļø Remember to like this post and leave your thoughts in the comments!
Posted on July 4, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
July 4, 2022