Real time App with React and WebSocket - Part 1

Learn how to build a simple Real Time Web Application with React and the WebSocket API. Part 1 : Introduction and Building the Front-end.

Real time App with React and WebSocket - Part 1
Photo by Visual Stories || Micheile / Unsplash

Learn the basics to make a Real Time web application with React and the WebSocket API. We will bootstrap a small Client-Server app.

This project is in 2 parts. Part 1 explains the goal and setup the front-end app. Part 2 explains how to setup the server and use Zustand as a state management tool.


Table of Content

Part 1: Building the frontend

  • Technologies Involved
  • Project Scope
  • Implementation
    • Step 1: Create a new Project
    • Step 2: Create the Front End

Part 2: Server & State

  • A Real Time Web Server
    • The HTTP Server
    • The Socket.IO Server
  • State Management with Zustand
    • Creating a Zustand store
    • Using the Store
  • Conclusion

Technologies involved

  • React - We'll be using the latest React version (16.13.1) as our web UI library.
  • Next.js - a modern solution to build React web applications.
  • Zustand - as a small yet efficient state management library. It will make it simple for us to subscribe to both state change and WebSocket (WS) events.
  • Socket.IO - as our WS implementation for the server and client.
  • Yarn Berry (yarn version 2)

Project scope

We will do simple tic-tac-toe game:

The server will provide the game state and an AI that you can play against.

WebSocket is an event based protocol, we will send event back and forth between our client and server. So let's start defining the rules and events for our game:

1/ The user can request to play a new game. This will emit a "new-game" event. The server will create a new game and return its state with the event "state".

2/ The user can emit "play" event with the box id as payload (0 to 8). The server will check if the play is valid and update the game by emitting an event "state".

3/ After the player plays a valid move, the server will play for the Bot and emit an event "state".

4/ After each move, the server checks if the current player won. If the winner information will be stored in the game state. If the game ends (player wins, loses or ties) the server will emit a "state" event.

6/ The player can start a new game at any moment by requesting a new game (emit "new-game")

Implementation

At any time you can see the full code in this GitHub repository.

First we will start with the project structure. Then continue with implementing a static front-end.
In Part 2 we will create our server and finally connect it with our front-end application.

Step 1: Create a new Project

Let's start by creating a new project with git and yarn 2:

$ mkdir tic-tac-toe && cd $_
$ git init
$ yarn set version berry
$ yarn init
$ git add . && git commit -m "initial commit"

These commands will:
- create a new project directory and navigate into it
- initialize a new git repository
- install yarn berry locally into the project
- generate a package.json file
- add and commit the changes

By default zero install should be configured (check the .gitignore file). If you wish, you can change this behavior before committing.

If you already have yarn berry installed globally, you don't have to install it locally and you could use yarn create next-app instead of most of the previous commands.

Step 2: Create the Front End

We will use Next.js for that. The motivation is because Next.js has become a popular Framework for React Apps which includes a lot of features and optimizations out of the box. One feature that will be useful for us in this project is the ability to extend the Next's server. We will leverage this feature to create our WebSocket server so we don't have to setup our own server.

Let's start with installing the dependencies. We can follow Next.js documentation to get the latest instructions:

$ yarn add next react react-dom

If Yarn Berry is not installed globally then yarn create next-app will use Yarn 1.x and You'll need to set it up and reinstall the dependencies.

The documentation then asks us to add the following scripts in package.json:

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start"
}
package.json

Now let's create our first page.

First, Next.js will search for a folder called pages and load each file as different pages. Next.js will search for the folder in the root directory or the src directory. I prefer having the source in the src directory, so I'll create the folder like this:

$ mkdir -p src/pages
$ ls -R
.:
package.json  README.md  src  yarn.lock

./src:
pages

./src/pages:

Then Finally, we can create our first index page:

import Head from "next/head";
import React from "react";

function Game() {
  return (
    <> 
      <h1>Tic Tac Toe</h1>
    </>
  );
}

function Index() {
  return (
    <>
      <Head>
        <title>Tic Tac Toe</title>
        <meta name="viewport" content="initial-scale=1.0, width=device-width" />
        <link
          rel="stylesheet"
          href="https://use.fontawesome.com/releases/v5.2.0/css/all.css"
          integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ"
          crossOrigin="anonymous"
        ></link>
      </Head>

      <Game />
    </>
  );
}

export default Index;
src/pages/index.tsx

I'm using Typescript for this repository so I'm creating a .tsx file instead of a .jsx one.

If we run yarn dev now, we will get the following error:

ready - started server on http://localhost:3000
It looks like you're trying to use TypeScript but do not have the required package(s) installed.

Please install typescript, @types/react, and @types/node by running:

        yarn add --dev typescript @types/react @types/node

If you are not trying to use TypeScript, please remove the tsconfig.json file from your package root (and any TypeScript files in your pages directory).

Next.js will notice that you're trying to use Typescript but we haven't installed the dependencies yet. Add them as asked: yarn add --dev typescript @types/react @types/node

Now we can run yarn dev again and navigate to localhost:3000/ :

$ yarn dev
ready - started server on http://localhost:3000
We detected TypeScript in your project and created a tsconfig.json file for you.

event - compiled successfully
event - build page: /next/dist/pages/_error
wait  - compiling...
event - compiled successfully

Our Next.js app should now be working fine!


If your IDE start throwing "module not found" errors, you might need to do some special configuration for it to work with Yarn Plug&Play system. The easiest way is to follow Yarn's documentation

For example : yarn dlx @yarnpkg/pnpify --sdk vscode  


Now  let's work a bit on the UI:

//Import our global style. We would use a styling library or use nextjs css features too (css modules or styled jsx)
import "../style.css";

import type { AppProps } from "next/app";
import React from "react";

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}
export default MyApp;
src/pages/_app.tsx
/* full styles: https://github.com/ArmandDu/tic-tac-toe-websockets/blob/master/src/style.css */
src/styles.css
import Head from "next/head";
import React from "react";

//move Game to its own file
import { Game } from "../components/Game";

...
src/pages/index.tsx
import React from "react";
import { Box } from "./Box";

enum BoxValue {
  EMPTY,
  BOT,
  PLAYER
}

enum Players {
  BOT,
  PLAYER
}

const MOCK_GAME = {
  winner: null,
  draw: false,
  grid: Array(9).fill(BoxValue.EMPTY)
};

const MOCK_ACTIONS = {
  startGame: () => console.log("Start Game"),
  play: (id: number) => console.log("user plays", id)
};

const playerLabel = {
  [Players.BOT]: <i className={"far fa-circle"} />,
  [Players.PLAYER]: <i className={"fas fa-times"} />
};

export function Game() {
  const game = MOCK_GAME;
  const actions = MOCK_ACTIONS;
  const turnIcon = playerLabel[Players.PLAYER];

  return (
    <div className="tic-tac-toe">
      <h1>Tic Tac Toe</h1>

      <div className="game">
        <div className="controls">
          <button onClick={actions.startGame}>New Game</button>
          {game ? (
            <span>turn: {turnIcon}</span>
          ) : (
            <span>
              Player ({playerLabel[Players.PLAYER]}) vs. Computer (
              {playerLabel[Players.BOT]})
            </span>
          )}
        </div>

        <div className="grid">
          {game?.grid.map((value, id) => (
            <Box key={id} value={value} onClick={() => actions.play(id)} />
          ))}
        </div>
      </div>
    </div>
  );
}
src/components/Game.tsx
import React from "react";

enum BoxValue {
  EMPTY,
  PLAYER,
  BOT
}

interface Props {
  value: BoxValue;
  onClick?: () => void;
}

const boxLabel = {
  [BoxValue.EMPTY]: { background: "", icon: "" },
  [BoxValue.PLAYER]: { background: "player-1", icon: "fas fa-times" },
  [BoxValue.BOT]: { background: "player-2", icon: "far fa-circle" }
};

export function Box(props: Props) {
  const { value, onClick } = props;
  const { background, icon } = boxLabel[value];

  return (
    <div
      className={`box`}
      onClick={onClick}
      role="button"
      tabIndex={0}
      onKeyPress={e => {
        (e.key === " " || e.key === "Enter") && onClick && onClick();
      }}
    >
      <div className={`box-content ${background}`}>
        {icon && <i className={icon} />}
      </div>
      <div className="box-preview"></div>
    </div>
  );
}
src/components/Box.tsx
import React from "react";
interface Props {
  children: React.ReactNode;
  open?: boolean;
}

  //this component will be used later when we connect the logic
export function AlertModal(props: Props) {
  const { open, children } = props;

  return open ? <div className="alert-message">{children}</div> : null;
}

export default AlertModal;
src/components/AlertModal.tsx

That's it for the UI part ! If we run the app again, we should be able to see a TicTacToe game and see the actions logged in the console.

Next we will create the server and connect them both using WebSocket and Zustand as a state manager: Real time App with React and web-sockets - Part 2