Tic-tac-toe game in React and Redux

Marcin Baraniecki
5 min readJan 15, 2017

Disclaimer: there are at least a dozen of implementations of tic-tac-toe game in React out there in the wild. One of them is even hosted by the official React’s tutorial (in fact I stumbled upon it after I finished building my own one). In my game, however, I wanted to also show the advantages of using Redux.

In this article I will show what paradigms I kept in mind while I was building the 2-players tic-tac-toe game in React and Redux. While it doesn’t sound like rocket science example (in fact the rules of the game are dead simple), I consider it a great example of component-based architecture with one, global state, acting as the only source of truth, being the ultimate data model which in the end gets mapped to the view.

Here’s the working demo. And here you can find the source code.

Mapping the data model to the view

I will not be introducing React in details. I assume that my readers at least know that it is Facebook’s view library, which in short words, is responsible for mapping your data model to the view.

Yes, let that sink in. The view is a “function” of a model. Ideally, there are no side effects. Ideally, that “function” is pure — for given shape of the data, as long as it remains the same, it will always be rendered the same way.

Before I show you the code, think about the tic-tac-toe game in terms of underlying data model. At the most basic level, we need to somehow represent the values (either blank, X, or O) assigned to the cells of 3x3 (3 rows, 3 columns) board.

One of the possible ways of defining the tic-tac-toe’s board data model

Now, imagine that there exists a function, which takes that board’s data as an input, and spits out it’s visual representation, which is way more pleasant to look at for our players. That is the role of React’s components.

We need to make sure that there is only one such model, and it is easily accessible for read-only purposes. We definitely don’t want anybody (or, precisely, any part of our code) who is not allowed to, to have a write-access (or right to change) to the model on any random occasion. While these are mostly general rules concerned with the global state (that’s how I will call the model now), Redux is a perfect guardian and law-enforcer for those.

Let’s recap:

  • Redux acts as a “guard” — you ask for the current shape of the state, it provides the data. Occasionally, it will also allow you to change the state, but that will come later.
  • React acts as a “translator” — you give the data, it returns the view.

The view components

React’s philosophy assumes that every data model can be mapped over to the hierarchical structure of view components, starting at single root. These are the atomic, building blocks, and ideally each one of them should be responsible for one thing — either acting as a container for other, specialised child components, or providing a visual representation of given chunk of the global state.

In our example, the game can be divided into few components. As you will see, one of them will be responsible for displaying the current symbol (or the result of the struggle), the other will display the board, while it’s children will take care of displaying either a blank field, an X or an O symbols.

Here’s an example of the Blank symbol component:

Well, it returns the blank div element, that’s what it was meant to do. Don’t mind the onClick part for now.

The game is a component tree. Here’s the high-level schema:

<App>
<Result />
<Board>
<-- <Blank>, <X> and <O> components here, depending on the data model-->
<Board />
</App>

The schema above depicts all the component types I had to use to build the game:

  • App — a root for the others,
  • Result — indicates which symbol’s turn it is, or whether some symbol won (or if there was a draw)
  • Board — acts more like a container for Blank, X and O components, which place themselves at the correct positions on the board,
  • Blank, X, O — represent the state of the given cell (the symbol that lives there, if any)

As you can see, this is the accurate set of building blocks, maintaining their single-responsibility roles. In fact, component-based architecture is not only a matter of React, but rather most major front-end libraries: Angular 2, good old Angular 1.5+, Vue and others.

Navigate to the src/components directory for detailed source code of each of the components.

Taking actions

Redux’s core idea is that each change to the application’s state will take place through action “messages” dispatched in it’s “context”. The game is dead simple — in fact I only had to define two types of actions:

  • adding a symbol to the blank cell,
  • restarting the game after either side won or if there was a draw.

While restarting the game in reality means “start over by clearing the board and set the winner to unknown yet”, adding a symbol to given blank cell is a bit more complicated.

Adding a symbol is parameterized action. It is required to define, what symbol (X or O) goes into which cell (row’s and column’s indices). There’s even more! When, based on this information, the new state’s shape is computed, a series of game’s logic checks need to be run as well. Here’s the essence of tic-tac-toe — adding next X or O might make one of the sides win the game, meaning that each row, column and slant must always be checked for containing the same symbol.

Here’s the reducer (a function that Redux uses to compute new state based on the action taken).

Game’s logic

The rules of the game assume that for given symbol, it is considered a winner, if three “instances” of that symbol occupy a row, a column, or one of two slants.

These rules had to be translated into series of plain JavaScript functions. In the end, only a single function is exposed for use, that takes the board as an input, runs a series of checks for each row, column and slant and returns the result:

The ‘line’ value is then used as a CSS class in order to highlight a winning line (row, column, slant).

This logic is also well-tested in a series of unit tests.

In summary

The project was kickstarted using great create-react-app project by React maintainers. Thanks to this boilerplate, I could focus on the nuts and bolts of the game, instead of spending countless hours configuring build pipeline.

Adding Redux was as simple as running few npm installcommands.

The X and O symbols on board are animated SVG elements. Although these are really basic and simple, I found myself having a lot of fun experimenting with css animations for SVG nodes.

One of my goals, was to not leave the code untested under any circumstances. While create-react-app provides us with the Jest testing framework (which is now really great, compared to it’s earlier versions), I also added Enzyme to enable shallow rendering of the components and test their output based on arguments passed as props. Don’t forget to check them out — the unit tests are located in same directory as the piece of the application that is being tested (components, reducer, game logic).

Building the game was a lot of fun, but also significant amount of time spent on making decisions that would keep the overall result simple and maintainable. I consider it to be a very good exercise for everyone who wants to improve his or her skills in overall thinking about the application’s architecture, state’s shape and actions management.

What are your ways of improving own understanding of the component-based architecture? :-)

--

--

Marcin Baraniecki

web dev @ SoftwareMill. Electric Vehicles enthusiast. Loves all types of night photography, including astrophotography.