Use Key On Door — On the Construction of a Single Page Application using React and Redux

Oliver Cawthorne
9 min readMar 1, 2021

The late 1960s, enjoying gradual increases year on year in computational power, gave birth to a more recreational form of computing. One particular game, Hamurabi (1968), was a text-based game intended to teach sixth-grade students of America in order to teach them basic economics, commissioned and funded by the U.S. Office of Education.

The interface for Hamurabi, built with the FOCAL programming language.

These games developed in complexity over time, peaked in popularity in the 1980s, then entered a gradual decline as computers well surpassed the computational power required to run these games. Instead, consumers turned to graphics-based games in the 1990s. Today, games such as Grand Theft Auto V and others beg the question for more modern consumers: “text-based games look ancient, there are no images or sounds. Why bother at all with it?”

To an extent, they would be right to ask. If you compare these sorts of games to a modern Triple-A title, it would be like reading a book about skydiving rather than going skydiving yourself. It’s less immersive and less interesting. On the plus side, however, should one get immersed into a text-based game, their imagination is the only thing that may limit their experience.

I’m not suggesting that I am an avid fan of text-based games; I do agree that they do belong to a bygone era. However, for my final project with Flatiron School, I wanted to take a break from the more serious tone of the rest of my projects and focus on a playable web-based application, named React-venture! after the technology which made this possible.

Starting with the frontend, I opted for a classic look with a few extra features: a sidebar detailing your inventory and the capacity to create accounts and save your progress.

The interface, prior to creating an account, detailing the context of the game. Up on the top left is the logo for React which actually comes with the blank React template, so it became something of a mascot for me.

Once a user logs in, they are able to play the game and interact with the environment as prescribed in the instructions.

On the other hand, the backend is powered by a fairly standard Rails set-up. Its primary function is to generate and provide data (which includes information about both users and the objects within the game, such as doors and crowbars). The data is generated from the backend to provide to the frontend through an Application Programming Interface (API).

So, the visuals and the premise are fairly simple. Let’s get more specific from the ground up.

The Backend: Ruby on Rails

As we all know, the backend handles all the raw data. It should be able to both send data to the frontend to be interpreted into information, and also receive information from the frontend and interpret this back into data.

Where there’s data, there’s a database. In this particular database, the schema contains the following three tables:

  • Users
  • Entities
  • Entity Interactions

Users

When playing the game, I expected the user to be able to save the game and thus commit their inventories to a data store. Initially, I had implemented a table, Inventories, which would hypothetically contain a user ID as a foreign key and an instance of an entity. This would be a cleaner option, although I opted instead to keep the names of items of a user’s inventory serialised as an array, stored directly in a user’s record.

A similar process was implemented for things like history (i.e. the records that show up with every command execution), known objects (not all objects are known to the user when they first play the game and must be discovered), broken objects (objects which can no longer be used for whatever reason), and unique events (differing from the others, a hash which stores Boolean values as to whether the user has progressed beyond a certain stage). I chose not to reference entire entities with all their properties for each user — I saw this to be unnecessary — and instead stored the names only.

Once signed up, a user starts with empty arrays for history, broken objects, and a fully “false” hash for unique events to denote they have not been achieved yet. They also start with a default array of known objects, which is what the user has to work with in the very beginning.

Entities

The objects within the game have fairly simple properties: a name, a description, a description for if the item is broken (not always applicable), the feel of the object, and Boolean values for whether the object is obtainable (keys are obtainable, doors are not obtainable), available (directly tying into the known objects stored within the user’s record), and broken (ditto for broken objects).

Entity Interactions

This is where the more obvious element of actual gameplay is stored. Without entity interactions, there is no game; you would only be able to look around the room and at individual objects. This stores the text values for two combining items, such as “door” and “key”, a text value for the result of combining these two items, an “action” (we’ll get to this later) and, just like in Entities, an “available” Boolean value as to whether these items can be combined yet.

It’s at this stage, once we design the individual objects and their interactions as seed data for the database, that we can store all of this information and refer to it much later in the frontend. With the help of Cross Origin Resource Sharing (CORS) and a few extra tweaks to the backend, the API is live and the frontend is ready to make requests.

So, what is the frontend doing?

The Frontend: React.js and Redux

Obviously, handling data and passing data around the frontend, let alone transmitting to the backend, is important. If you type “get crowbar” in the entry field, you want 1) recognition in the main panel that you’ve picked up the crowbar, and 2) a reflection of this inventory change on the inventory panel on the left. We don’t want a situation where the inventory will only update if, say, we refresh the page, we want live updates all the time. All possible through JavaScript, made easy with React, and made even easier with Redux!

At Flatiron, we were taught React first and Redux later. However, for the sake of brevity, I will explain Redux first using the following image as a tool.

I referred to this image countless times while getting familiar with Redux, but it roughly goes as follows:

  1. A user-induced change occurs on the frontend, such as grabbing an entity. Whatever function is listening to this event will prompt the creation of an action.
  2. An action simply returns an object with at least one key/value pair where the key is “type” and the value is a string which resembles an instruction, such as { type: "SUBMITTED_COMMAND", command } (note that the command itself is stored as a key/value pair in the object. Thanks to ES6, this hash is equivalent to { ... command: command }).
  3. This action is directed, through the dispatcher, to its assigned reducer.
  4. The reducer takes the object and determines its type. With this type, it makes the decision for what the state of that section of the application should be and returns it. (note that, however, it does NOT directly manipulate state.)
  5. Redux’s createStore method processes this reducer, and other reducers, amalgamating it into the overall state of the application. With this, the state has changed, the view is re-rendered, and the user is free to make more changes and repeat the cycle. The state is stored in a Redux store which contains, essentially, getters and setters for the state.

The reason why all this is so important is because the data is so easily accessible from anywhere in the application at any given time. React, being structured the way it is, has enormous advantages from a User Interface perspective and from a JavaScript functional perspective (you don’t need to scroll through thousands of lines of HTML and deal with finicky JavaScript scripts here and there) but, alone, it struggles with data management.

The premise of React is that a webpage is split into components, where the overarching component is normally called “App” and is subsequently divided into child components, perhaps such as “Sidebar”, “Entry Field” and “History” as is the case in my project. Further, the Sidebar has more child components such as inventory and a welcome/log in/sign up screen.

The issue, however, is how everything communicates with one another in terms of state changes and data. If a user logs in and wishes to retrieve their history, the data must pass through parent to child always, without exception. So, if we want to update elements of the History component through the Log In component, the data would have to first travel to the Sidebar, then from Sidebar to App, and from App to History. That’s a long way, and is very cumbersome in larger projects.

The beauty of the Redux store, however, is that any component can connect to it and use whatever information it likes in there, as well as instructing the change of whatever data it wills. So, the issue of data traversing from parent to child is completely eliminated and React serves, for a decent portion, as an extreme UI aid with the benefit of highly versatile JavaScript behind it.

Let’s look at those five stages again in a specific case where it must interact with the backend.

  1. A user types “get crowbar” into the entry field and submits. The entry field is cognizant of a wide array of information which it will later use, but for now we only need to focus on userObjects and userHistory. These props connect to the Redux store with the action submitEntryField.
  2. The handleSubmit method within the Entry Field component will execute this submitEntryField action, taking the current command as its sole argument.
  3. The action returns the object { type: "SUBMITTED_COMMAND", command }, ready for the commandReducer.
  4. The reducer finds the case where the type is equal to "SUBMITTED_COMMAND" and checks whether or not the user has completed the game. If not, the victory message is not displayed and the process continues.
  5. The first word, “get”, is recognised as the action. Alternatively, “pick” and “grab” work for these triggers (in a JavaScript pattern known as fall-through). The item in question is the final word (this means that you could hypothetically type in “pick up the crowbar” and it would still work.)
  6. Three checks are performed on the entity: whether the player knows it, whether the player has it already, and whether the entity even exists. In this case, let’s say the player has not got the crowbar yet.
  7. The final check is whether the entity is obtainable. A crowbar is obtainable, so a brand new copy of the state will be returned by the reducer, where userObjects has “crowbar” added to it and userHistory has some dialogue about picking up the crowbar added to it.
  8. This copy of the state is sent to the store where the state can be modified to reflect these changes.
  9. The player chooses to save their game, and a fetch request (“POST”, specifically) is made to the backend API to store their brand new history and inventory.

The final project for Flatiron School was a delight to build and, as expressed earlier, was a nice change from the more serious tone of most of my projects. React and Redux can be very mind-boggling at times to understand, but with exposure (and plenty of visits to StackExchange), the simplicity and necessity of these two technologies becomes extremely apparent.

Some early gameplay of React-venture!

--

--

Oliver Cawthorne

Physics student turned accountant turned software engineering student.