The endless grazing fields of Firebase triggers

I was looking for a nice API to have some NotSoFancyAppChallenge, something to practice React API calls. While looking the thought of creating my own API kept popping up. But it was too much work for an unfancy challenge: server, node, express, DB, API, authentication, and so on. Then I recalled years ago a short experiment with Firebase. I still remembered the disappointment finding out you can’t get a random element. But years had passed, and I finally got over it 🙂

Once I started reading the Firebase documentation, I trow the plan to create a simple “CRUD APP” and replaced it with an interactive game: a NotSoFancy Tic Tac Toe game challenge.

The goal was to have a React Hooks multiplayer game that has auto-update once the other player made a move. Without a server, just using the Firebase magic database triggers.

Step 1: Creating a Firebase DB

I’m not going to document Firebase, just to present the JSON uploaded to FB. It was a game in progress made for testing purposes.

{
  "games" : {
    "2" : {
      "board" : [ "", "", "X", "", "", "", "Y", "", "" ],
      "turn" : "X"
    }
  }
}

And I know, there a better way to save the board, but for the POC I chose clearance above efficiency.

Authentication

Since it’s just a POC, I provided writing permission (roles) only to my Gmail account:

{
  "rules": {
    ".read": true,
    ".write": "auth.uid=='XXXXXXX'"
  }
}

And used Firebase auth code to identify the user:

var ui = new firebaseui.auth.AuthUI(firebase.auth());
firebase.auth().onAuthStateChanged(function (user) {
    if (user) {
        document.querySelector("#firebaseui-auth-container").innerHTML = user.displayName;
    } else {
        ui.start('#firebaseui-auth-container', {
            callbacks: {
                signInSuccessWithAuthResult: function (authResult, redirectUrl) {
                    return true;
                },
                uiShown: function () {
                    if (!!firebase.auth().currentUser) {
                        document.querySelector("#firebaseui-auth-container").remove();
                    }
                }
            },
            signInFlow: 'popup',
            signInOptions: [
                firebase.auth.GoogleAuthProvider.PROVIDER_ID
            ]
        });
    }
});

Firebase wrapper

Next up is the JS connection to Firebase and some simple JS code that wrapped the FB methods:

firebase.initializeApp(firebaseConfig);
let myBase = {
    get: (path) => {
        return firebase.database().ref(path).once('value').then(function (snapshot) {
            return snapshot.val();
        })
    },
    getList: (path) => {
        return firebase.database().ref(path).once('value').then(function (snapshot) {
            const data = snapshot.val();
            return data;
        })
    },
    save: (path, obj) => {
        return firebase.database().ref(path).set(obj, function (error) {
            if (error) {
                console.log(error);
            } else {
                console.log('Data saved successfully!');
            }
        });
    },
    listener: (action, path, callback) => {
        return firebase.database().ref(path).on(action, snap => {
            callback(snap.val());
        });
    }
}

Javascript Tic Tac Toe class

Finley, we can get to the game itself. The code is very simplistic and straight forward:

class Game{
    path = "game/"
    id;
    board = new Array(9).fill(0);
    player = 'X';
    turn = 'X';
    onchange = () => {};

    constructor(id,player,onChange){
        this.player = player;
        this.onchange = onChange;
        if(id){
            this.id = id;
            this.get();
            myBase.listener('value',this.path + this.id, res=>{
                this.fromDb(res);
            })
        }else{
            this.id= "g-" + Math.round(Math.random() * 1000000);
            myBase.save(this.path + this.id,this.toDb())
        }
    }
    fromDb(res) {
        this.turn = res.turn;
        this.board = res.board;
        this.onchange({});
    }
    get() {
        myBase.get(this.path + this.id).then(res => {
            this.fromDb(res);
        })
    }
    move(loc) {
        if(this.player == this.turn){
            this.board[loc]=this.player;
            this.turn = this.turn == 'X' ? 'Y': 'X';
            myBase.save(this.path + this.id,this.toDb())
        }
    }

    toDb() {
        return {
            board: this.board,
            turn: this.turn
        }
    }

    isOver(){
     ...
     ...
     ...
   }
}

As you can see, there is a listener that updates the page whenever the other player done a move.

Loading or creating a new game is simple as:

const newGame = new Game(null,'X',{});

const gameId = "2";
const currentPlayer = 'X';
const existingGame = new Game(gameId,currentPlayer,{})

One dirty trick I used is the third property, an onchange value. This will send the React app a new object and will trigger and reload the UI. Changing the UI once the game was loaded or reloaded from the DB. Next time this will be replaced with Redux or Recoil.

React Hooks Tic Tac Toe loader

  1. A simple form to load an existing game, or open a new one.
  2. The onChange listener to reload the UI.
  3. The GameBoard element that presents the Tic tac Toe itself.
const MasterGame = () => {
    let [change,setChange] = useState({});
    let [game, setGame] = useState({});
    let [gameId, setGameId] = useState({});
    let [player, setPlayer] = useState({});

    const loadGame = () => {
        setGame(new Game(gameId,player,setChange));
    }
    const newGame = () => {
        setGame(new Game(null,'X',setChange));
    }
    return (
        <>
            <input placeholder="gameId" value={gameId} onChange={(e)=setGameId(e.element.value)>} />
            <input placeholder="player" value={player} onChange={(e)=setPlayer(e.element.value)>} />
            <input placeholder="player" id="player" />
            <input type="button" value="load" onClick={loadGame} />
            <input type="button" value="new game" onClick={newGame} />
            {game.id? <GameBoard game={game} /> :null}
        </>
    )
}

React Hooks Tic Tac Toe code

And the last part is the game itself:

const Cell = ({value, index ,click}) => {
    const clickable = <p className="clickable" onClick={()=>click(index)}>{value}</p>;
    const noneClick = <p>{value}</p>
    const cube = click? clickable: noneClick;
    return cube;
}

const GameBoard = ({game}) => {
    const canClick = game.turn!=game.player? null : (i) =>{
        game.move(i);
    }
    return (
        <>
        <h3>id: {game.id}: {game.turn}: {game.board.join()}</h3>
            <div className="board">
            {game.board.map((cell,i) =>  <Cell key={i} value={cell} index={i} click={cell == ''? canClick : null} />)}
        </div>
        </>
    )
}

Conclusion

Once I found out how to allow JS code from different computers to connect and trigger changes, the appetite is getting bigger, and the next game will probably be written very soon.

Update / Some links:

After done writing, I saw the post “How To Build Your Own Comment System Using Firebase” by Aman Thakur. He pointed out the auto-id for collection, done by “add” instead of “set”.