ReactJS Training: Creating your first game with React and TypeScript

Mariano Vazquez
Jan 20 · 16 min read
Image for post
Image for post

Si quieres leer la versión en Español de este artículo, haz click aqui.

Now that we have covered the basics, it’s time to make things real. In this exercise, we will set up a React application from scratch, and then we will implement the Connect Four game on top of it.

Image for post
Image for post
This is the game we are going to build in this post: Connect four

By following this step-by-step walkthrough, you will learn about React and TypeScript while you code a real-life application. Are you ready? 👾

Initial setup

Note: If you want to start right away with the game logic, you can skip this section and open the begin application of this Exercise, located in this training’s GitHub repository. Remember to use npm install before running it with npm start.

As we explained in the previous post, neither TypeScript nor JSX runs in browsers. And since we are going to write code in these languages, we need to transpile it before executing our application. To do this, we have two options:

In this post, we are going to go with the latter option, leveraging Facebook’s Create React app, which is the de-facto tool to build React applications nowadays.

And it’s super simple to set up:

In your terminal, run npx create-react-app connect-four --typescript. This command will create a TypeScript application inside the folder connect-four. Wait for the process to complete. You should see a message similar to this:

Image for post
Image for post
Create React app installation completed successfully message

Note: If npx doesn't work, try withnpm i -g create-react-app and then create-react-app connect-four --typescript.

Browse to the connect-four folder you’ve just created, and take a minute or two to analyze the folder structure. You are looking at a fully-functional application with the business logic inside the src folder:

connect-four
├── node_modules
│ ├── ...
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
│ └── ...
├── src
│ ├── App.css
│ ├── App.tsx
│ ├── index.css
│ ├── index.tsx
│ └── ...
├── package.json
├── tsconfig.json
└── ...

Now, run npm start.This command executes the app in “development mode”, which provides a lot of perks like automatic reloading when you make changes to the code (a.k.a. Hot Module replacement).

Open up http://localhost:3000 to visualize your application inside the browser:

Image for post
Image for post
Create React app initial Home screen

Congratulations! You’ve created your first application with React and TypeScript 👏 💃 🕺 👏

Let’s take it for a spin. Open the app with VSCode or the IDE of your preference and navigate to the src/App.tsx folder.

Image for post
Image for post
App.tsx file autogenerated by Create React app

Note: Pro tip! You can open VSCode pointing to the folder your terminal is by running code .. Similarly, you could do the same for Atom with atom .. And for Sublime, you can run subl ..

Take a couple of minutes to analyze the code in this file:

With the app running locally (if you have stopped it, run npm start in your terminal), modify the code by removing the </div> closing tag in line 22, and save your changes.

Note that VSCode (or your IDE) now displays an error:

Image for post
Image for post
Error message displayed in VSCode

And the browser displays a compilation error:

Image for post
Image for post
Compilation error in the browser

Fix the error by undoing what you did (we removed the </div> closing tag), save your changes and wait for the browser to refresh your app.

Now, open the src/index.tsx file. This is the main entry point of the application:

import React from “react”;
import ReactDOM from “react-dom”;
import App from “./App”;
ReactDOM.render(<App />, document.getElementById(“root”));

The most important things you need to learn now are:

This file has exactly the same content as the previous basic examples we saw in the previous post. This is the power of React: regardless of the complexity in your app, the code to render it is the same.

Wrapping up

In this quick walkthrough of the initial set up, we did the following:

Last, don’t forget that browsers only understand HTML, JS, and CSS, not TS or JSX. This scaffolded app has a build process that will generate JS and CSS files and will place it inside of a dist folder, and referenced in the index.html file, also generated. And these autogenerated files will be sent to the browser to parse, read, interpret and execute.

Note: If you are interested in reading a deep-dive explanation of the transpilation process, and how to configure tools for this, add a comment in this post and I’ll write it!

Adding the game logic to the app

Now that we understand the foundations of a React app, it’s time to add the game logic. As we explained in the previous post, React applications split the business logic into different components. But there are different responsibilities we’ll have to handle: the logic that decides who won (and if someone won), the logic that selects the elements to draw (and how), the one that determines whose player has the turn to make a move, etc. How can we split up these responsibilities in a consistent repeatable way?

We’ll use a widely-known pattern, Presentational and Container components, to organize our components in a simple, but yet powerful structure:

Image for post
Image for post
React data flows from “top to bottom”, while events bubble up information “from the bottom to the top”

This technique proposes to encapsulate all the business logic and state in Parent components (Container or Smart). And use their Children's components, usually the leaves, for rendering the UI and managing the user interaction (Presentational or Dumb components).

Container components send both data and functions to their children via props. Presentational components use the data to decide what and how to draw. And execute the functions when the user interacts with them, usually sending information as parameters.

Note: since your app’s will end up cascading the information from top to bottom, this approach is best suited for small/middle-sized apps. Large applications composed of a deeply nested hierarchy require a different approach. We’ll talk about it in a next post.

By following this technique, we can identify the following entities:

Image for post
Image for post
Split the application’s logic into small components

Of course, the components you use can vary depending on your preference. Can you think of a different way of organizing your code?

Creating a Tile component

Create a new folder named components inside of the src folder.

In this folder, create another folder named Tile, and inside it add the following (for now empty) files:

Open up the src/components/Tile/types.ts file and paste the following code:

export interface Props {
id: string;
chipType?: string;
onClick: (id: string) => any;
}

By typing the Tile component’s props, we define its interface, or contract. It tells the component consumer that:

Then, open the src/components/Tile.tsx file and paste the following code:

import React from "react";
import classNames from "classnames";
import styles from "./Tile.module.css";
import { Props } from "./types";
export default class Tile extends React.PureComponent<Props> {render() {
const { id, chipType, onClick = () => {} } = this.props;
const chipCssClass = classNames(styles.chip, chipType === "red" ? styles.red : styles.yellow);

return (
<div className={styles.tile} onClick={() => onClick(id)}>
{chipType && <div className={chipCssClass} />}
</div>
);
}
}

By looking at this code you’ll notice that the Tile component is a presentational component in charge of drawing tiles on your board. It decides if a Chip is present by checking the value of the chipType prop, and it sets the CSS class based on its value. Last, when clicked, it triggers the function set to the onClick prop, sending the Tile’s id as parameter.

Note: Have you noticed that we attached the Props interface to the React.PureComponent definition? This is how you type React class. The IDE will understand this and will tell you the type of each of the props in the compoennt. You can see this by hovering you your mouse over this.props value in the first line of the render() method. Give it a try!

Last, open the src/components/Tile.module.css file and paste this CSS code:

.tile {
width: 75px;
height: 75px;
border: solid 10px #3355ff;
border-radius: 100%;
background-color: white;
}
.chip {
width: 75px;
height: 75px;
border-radius: 100%;
background-color: gray;
}
.yellow {
background-color: #ffff33;
}
.red {
background-color: #ff010b;
}

Note: Create React app treats CSS files using [name].module.css differently from a regular CSS file, by transpiling them using the CSS Modules library. The main benefit of this is that you don’t need to worry about CSS class name clashing, as each file can be treated as an isolated module. This can be achieved because, when transpiling files, all CSS class names are replaced with a “unique” value of the format [filename]_[classname]__[hash].

For more information about this library, click here.

Creating a Column component

Now navigate to the components folder and create a new folder inside named Column.

Inside this folder, create the following files: a Column.module.css file to store the CSS code, a Column.tsx file for the React’s component logic and a types.ts file for the TypeScript types of the component.

Open up the src/components/Column/types.ts file and paste the following code that defines the props (contract) of the Column component:

import { ChipsPositions } from "../App/types";export interface Props {
column: number;
rows: number;
chipsPositions: ChipsPositions;
onTileClick: (id: string) => any;
}

This code tells the component’s consumer:

Open up the src/components/Column.tsx file and paste the following code:

import React from "react";
import Tile from "../Tile/Tile";
import styles from "./Column.module.css";
import { Props } from "./types";
export default class Column extends React.PureComponent<Props> { render() {
const { column, rows, chipsPositions, onTileClick } = this.props;
const tiles = [];

for (let row = 0; row < rows; row++) {
const tileId = `${row}:${column}`;
const chipType = chipsPositions[tileId];
tiles.push(
<Tile
key={tileId}
id={tileId}
chipType={chipType}
onClick={onTileClick}
/>
);
}
return <div className={styles.column}>{tiles}</div>;
}
}

This (also presentational) code renders a <div> element containing as many Tile components as the rows value indicates (sent via props). Each tile will receive a chipType and the onTileClick() function. Notice that the unique tileId is defined here by combining the values of row and column.

Last, open the src/components/Column/Column.module.css file and paste the following CSS code:

.column {
display: flex;
flex-direction: column;
cursor: pointer;
}

We are almost there! 🙌

Creating a Board component

Similarly, navigate to the components folder and create a new folder inside named Board.

Inside this folder, create the following files: a Board.module.css file to store the CSS code, a Board.tsx file for the React’s component logic and a types.ts file for the TypeScript types of the component.

Note: Are you seeing a common pattern when creating components?

Open up the src/components/Board/types.ts file and paste the following code that defines the props (contract) of the Board component:

import { ChipsPositions } from “../App/types”;export interface Props {
columns: number;
rows: number;
chipsPositions: ChipsPositions;
onTileClick: (id: string) => any;
}

This code tells the component’s consumer that:

Then, open the src/components/Board.tsx file and paste the following presentational code:

import React from "react";
import Column from "../Column/Column";
import styles from "./Board.module.css";
import { Props } from "./types";
export default class Board extends React.PureComponent<Props> {

renderColumns() {
const { columns, rows, chipsPositions, onTileClick } = this.props;
const columnsComponents = [];
for (let column = 0; column < columns; column++) {
columnsComponents.push(
<Column
key={column}
column={column}
rows={rows}
chipsPositions={chipsPositions}
onTileClick={onTileClick}
/>
);
}
return <>{columnsComponents}</>;
}
render() {
return <div className={styles.board}>{this.renderColumns()}</div>;
}
}

This code is similar to the Column component’s code, but instead of creating Tiles, we create multiple columns, passing the required information to them, and then we render the result. Thethis.renderColumns() method encapsulates this logic.

Have you noticed that we also use React.Fragment here? Probably not because we are using the shorthand “<></>”, which is an equivalent of “<React.Fragment></React.Fragment>”.

Last, open the src/components/Board/Board.module.css file and paste the following CSS code:

.board {
display: flex;
flex-direction: row;
border: solid 5px #002bff;
border-radius: 5px;
background-color: #3355ff;
}
.columns {
display: flex;
flex-direction: row;
}

Creating the App component

We are now going to develop the main logic for our game. Pay special attention to this section

Create a folder named App inside the src/components folder. Inside this folder, create the App.module.css file, the App.tsx file, and the types.ts file.

Open up the src/components/App/types.ts file and paste the following types:

export interface ChipsPositions {
[key: string]: Player;
}
export type Player = "red" | "yellow" | "";export interface Props {
columns: number;
rows: number;
}
export interface State {
chipsPositions: ChipsPositions;
gameStatus: string;
playerTurn: Player;
}

Here it is defined:

Now, open up the src/components/App/App.tsx and paste the following code:

import React from "react";
import Board from "../Board/Board";
import { Props, State, ChipsPositions } from "./types";
import styles from "./App.module.css";
export default class App extends React.PureComponent<Props, State> {
state: State = {
chipsPositions: {},
playerTurn: "red",
gameStatus: "It's red's turn"
};
calculateGameStatus = (playerTurn: string, chipsPositions: ChipsPositions): string => {
// TODO
};
handleTileClick = (tileId: string) => {
// TODO
};
renderBoard() {
const { columns, rows } = this.props;
const { chipsPositions } = this.state;
return (
<Board
columns={columns}
rows={rows}
chipsPositions={chipsPositions}
onTileClick={this.handleTileClick}
/>
);
}
renderStatusMessage() {
const { gameStatus } = this.state;
return <div className={styles.statusMessage}>{gameStatus}</div>;
}
render() {
return (
<div className={styles.app}>
{this.renderBoard()}
{this.renderStatusMessage()}
</div>
);
}
}

This is the basic structure of the component: presentational logic to draw/render the Board and the Status message, and a default App’s state. This code is completely functional, but the app still won’t react if the user interacts with the game. We’ll code this logic in the next few lines.

Implement the handleTileClick() method to react when the user clicks on a Tile.

handleTileClick = (tileId: string) => {
const { chipsPositions, playerTurn } = this.state;
// Get the last empty tile of the column
const column = parseInt(tileId.split(":")[1]);
let lastEmptyTileId = this.getLastEmptyTile(column);
// If there is no empty tile in the column, do nothing
if (!lastEmptyTileId) {
return;
}
// Add chip to empty tile
const newChipsPositions = {
...chipsPositions,
[lastEmptyTileId]: playerTurn
};
// Change player turn
const newPlayerTurn = playerTurn === "red" ? "yellow" : "red";
// Calculate game status
const gameStatus = this.calculateGameStatus(newPlayerTurn, newChipsPositions);
// Save new state
this.setState({ chipsPositions: newChipsPositions, playerTurn: newPlayerTurn, gameStatus });
};
getLastEmptyTile(column: number) {
const { rows } = this.props;
const { chipsPositions } = this.state;
for (let row = rows - 1; row >= 0; row--) {
const tileId = `${row}:${column}`;

if (!chipsPositions[tileId]) {
return tileId;
}
}
}

Take a couple of minutes to understand what the code does:

Last, implement the calculateGameStatus() method by pasting the following code inside the App component. The code contains the logic that decides who the winner is, or who plays next:

calculateGameStatus = (playerTurn: string, chipsPositions: ChipsPositions): string => {
const { columns, rows } = this.props;
// Check four in a row horizontally
for (let row = 0; row < rows; row++) {
let repetitionCountStatus = { playerChip: "", count: 0 };
for (let column = 0; column < columns; column++) {
const chip = chipsPositions[`${row}:${column}`];

// If there is a chip in that position, and belongs
// to a player, count that chip for that player
// (either increase the count or start over)
if (chip && chip === repetitionCountStatus.playerChip) {
repetitionCountStatus.count++;
} else {
repetitionCountStatus = { playerChip: chip, count: 1 };
}
// If the count for a player is 4, that player won
if (repetitionCountStatus.count === 4) {
return `Player ${repetitionCountStatus.playerChip} won!`;
}
}
}
// Check four in a row vertically
for (let column = 0; column < columns; column++) {
let repetitionCountStatus = { playerChip: "", count: 0 };

for (let row = 0; row < rows; row++) {
const chip = chipsPositions[`${row}:${column}`];
// If there is a chip in that position, and belongs
// to a player, count that chip for that player
// (either increase the count or start over)
if (chip && chip === repetitionCountStatus.playerChip) {
repetitionCountStatus.count++;
} else {
repetitionCountStatus = { playerChip: chip, count: 1 };
}
// If the count for a player is 4, that player won
if (repetitionCountStatus.count === 4) {
return `Player ${repetitionCountStatus.playerChip} won!`;
}
}
}
// TODO: Check four in a row diagonally

return `It's ${playerTurn}'s turn`;
};

Did you notice that this code does not check for four consecutive chips of the same value in diagonal? Can you come up with an implementation for this? If you do, send it to me as a Pull Request!

Initializing the app

Open up the src/index.tsx file and replace its content with the following code:

import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
import "./index.css";
// Initialize the app with 7 columns and 6 rows
ReactDOM.render(
<App columns={7} rows={6} />,
document.getElementById("root")
);

Start the app by running npm start in a terminal.

In the newly opened browser window, open the Developer Console, and then click the Components tab. You will see the hierarchy tree of your React application, composed of the components you’ve just created:

Image for post
Image for post
Connect Four app hierarchy tree in the Developer Console

Play with the game a little bit, add a few chips into the board, and then check the different Tiles of the board in the Developer Console. Notice that the properties received changed after you interacted with them.

Image for post
Image for post
When you click on an empty tile, its chipType changes

Note: You can also change a prop directly by modifying its value in the right panel. Try it yourself by turning a Tile’s chip type from "red" or undefined to "yellow".

Congratulations! You have just created your first game with React and TypeScript 💪💪💪

Wrapping up

In this exercise, we learned the following:

🎉🎉

Remember that you can find the full training in this GitHub repository.

JavaScript In Plain English

New JavaScript + Web Development articles every day.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store