Component Breakdown & State Management āš™ - Building a tic-tac-toe game with React from scratch

alimansoorcreate

Syed Ali Mansoor

Posted on July 4, 2022

Component Breakdown & State Management āš™ - Building a tic-tac-toe game with React from scratch

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:

User flow diagram

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:

Game screen with player names labelled

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:

Now using a component with different parameters 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:

  1. Start
  2. Settings
  3. 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

Start component

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

Settings component

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

Game component

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.

Game component result state

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"
Enter fullscreen mode Exit fullscreen mode
  • Grid size: the number 3, 4, or 5
type GridSize = 3 | 4 | 5
Enter fullscreen mode Exit fullscreen mode
  • 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
}
Enter fullscreen mode Exit fullscreen mode
  • 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
}
Enter fullscreen mode Exit fullscreen mode
  • Current player: number, either 0 or 1, representing the index of the current player
type CurrentPlayer = 0 | 1
Enter fullscreen mode Exit fullscreen mode
  • 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
}
Enter fullscreen mode Exit fullscreen mode

We now have ourselves the following global state tree:

{
  gameMode,
  gridSize,
  players,
  grid,
  currentPlayer,
  gameEnd
}
Enter fullscreen mode Exit fullscreen mode

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!

šŸ’– šŸ’Ŗ šŸ™… šŸš©
alimansoorcreate
Syed Ali Mansoor

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