Building games with React Native: Setting yourself up for repeated success

I hear people say “React (native) is not a good choice for making games!” and I call bullshit on that. I will prove to you that React Native is great for making games, depending on your choice of games and expertise with the library.

When people say that React Native is not suited for making games, they are probably thinking along the lines of Fortnight, Minecraft or Battle Chess. But even though I have to agree that Unity and other tools are better suited for making 3D games, you wouldn’t need a fully blown 3D games engine/framework to build a game like words with friends, Candy Crush or most of the idle clicker games out there.

Photo by Yevgen Tarasov on Unsplash

Many developers treat React & Javascript as the “Golden Hammer”, turning every project they encounter into a nail.

This is the first part of a new series of articles, guiding us through the process of making games with react native. We will look at common design patterns used in gaming, UI sets for different kind of games and how to use the advantages of React to build performant and hopefully even fun games.

Part 1: > You are here <
Part 2: Creating an intuitive and invisible UX

You can find the finalised code from this tutorial at the bottom.

Setting Goals

We need to plan ahead and try to figure out what we’re going to need. Humans are terribly bad at predicting the future so we will look back at the past and see what we will probably need in any case.

  • a splash screen
  • a loading / login / start screen
  • user management
  • a way to store player scores and progress
  • version management
  • a game

As you see, I’ve only mentioned the game at the very end without being too specific. The reason for that is that most games have a lot in common that most people don’t even think about when looking at games. We need an infrastructure to work with and to build our game into. If we decide to throw away our current “game” and try a new one, we probably only have to make slight adjustments to the infrastructure part of our project.

If we look at our current requirements, it’s apparent that we need to make a decision on our navigation model and how to store data between screens. Now, I know that there’s currently a great hype to remove Redux and other dependencies from all projects but I’m still a firm believer in the ideas behind Redux and especially with the newest version of React Navigation (5.x at the time of writing), they improved the integration of Redux in the Navigator so we’ll make good use of that.

At the end of this article, I’ll include a mini-game as a placeholder but we are going to replace that in the next instalment of this series so don’t hold it too dearly.

Prerequisites

To get started, I’m assuming that you have basic knowledge of React, React Native and Redux. To make building, bundling and the initial setup a bit easier, I’m also working with Expo. For the javascript part, I’m also going to use a few tools from current versions of ECMAScript (mainly ES6) but I’ll provide useful links where possible to get you up to speed. We will touch the topic of navigation in this article so you should be good to go without prior knowledge in that area.

You should have node/npm installed on your machine and know how to use a terminal.

I strongly recommend working with a versioning system. I’m using GitHub and I’ll link all commits in this article as well as the finished project after each article so you can always check out my code there and see if something is missing.

Getting started

As I mentioned, we’re going to work with Expo so let’s install their expo-cli.

Expo CLI is a tool for developing apps with Expo. In addition the command-line interface (CLI) it also has a web-based graphical user interface (GUI) that pops up in your web browser when you start your project — you can use this if you’re not yet comfortable with the using terminal or just prefer GUIs, both have similar capabilities.

We’ll quickly install the cli and use that to initialise our new project.

# Install the command line tools
npm install --global expo-cli

# Create a new project
expo init my-new-project

We’ll choose the Managed Workflow with a blank project. This way we don’t have any boilerplate code that we don’t need and still get the full benefits of the managed workflow. Using the managed workflow means that we won’t need to touch Android Studio or Xcode and still get access to many features and APIs made available through Expo. You can always choose to “eject” the project later on if you need to use some API or self-written native code that’s not available in the managed workflow but for now, this is all we need.

A few words about Expo
You will need to sign up for an Expo account to use the tools. This shouldn’t take long but you will have to sign in to use the Expo CLI and Expo client apps on Android and iOS.

You can run the app we are building on either your own device or an emulator of your choice. Testing the iOS version of the app requires an actual iPhone or a Mac to run the iOS emulator via Xcode.

Incase you run into trouble with the installation of expo, I advise you to check the official documentation for Expo, where they give tips on some possible problems on Macs, usable emulators to run the app on your machine and other obstacles.

First Steps

To check if everything went well, navigate to your project and run npn start in the root folder.

cd my-new-project
npm start

This will start the Expo CLI in the terminal AND open the web view. Both the command line tools and the web interface have roughly the same capabilities. I usually choose to use the web interface as it’s very convenient but either is fine.

Expo CLI in my IntelliJ terminal (left) and chrome browser (right)

Expo Web is currently in Beta so I’m gonna use the iOS simulator. For local tests on Android, I can recommend the Nox Player.

Starting the app in our emulator or device of choice, we’ll see a blank screen with only one line of text. If you see that, great. We can now start with the actual programming.

Before we start, make your first commit to git to create a save point for later. If anything goes awry, you can always return to this step and start again.

Don’t say I didn’t warn you if you skip this step!

GitHub Commit:
Initial Commit after Expo Setup

Make yourself at home

Ok, we’ve got it working and can check out the result in the Expo client. Let’s make some changes to create a homely feeling. Redux is a good starting point.

Install redux and react-redux via npm install redux react-redux and create a src/ folder where we will add our redux code. I assume that you are already comfortable with Redux so feel free to copy my redux folder to save some time and get a basic setup.

Overview of the Redux setup under src/redux/

Again, if you want to know how to setup redux with no hassle and minimal extras, I’d advice you to read this short article on Redux.

To wrap our app in the Redux store provider, we need to touch the App.jsx and import our redux store.

GitHub Commit: Added Redux

We need rules

While coding the last part, I realised that the default coding styles of the Expo project and my own preferences don’t add up and that all the line indentations are “wrong” and I would have to fix all that to keep my OCD in check… why not make use of an .editorconfig file and let the IDE know how I like my code to be indented and formatted. And while we’re at it, let’s add some linting to our project.

To get started with linting, we’ll need to install both eslint and a bunch of smart presets that we’re gonna use.

npm install -D eslint babel-eslint eslint-config-airbnb eslint-plugin-jsx-a11y eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks

For the brevity of this article, I won’t post the config files here but make sure to grab my .editorconfig and .eslintrc file from GitHub and add them to the root of your project. The last thing to do is to add a shortcut to our package.json.

Running npm run fix will check all .js and .jsx files in /src and apply our linting rules to them. Now that’s comfortable.

GitHub Commit: Added eslint and editorconfig

Time to add some actual code

We haven’t yet checked if our Redux implementation is running correctly and our app still looks a bit bland. Let’s create a component that’s connected to our store and shows the project’s name and Version.

If you take a look at our running app now, you’ll see that our Project still needs a name but yay, we are reading values from the Redux store so all is well. I’ll quickly set a project name and we can move on.

GitHub Commit: Added VersionTag component

Time to navigate

Our app is still a one-liner with no pages whatsoever so the logical next step would be to build a few pages and link them in a meaningful way. For now, I’ll create just a bunch of scenes that we can navigate to. We’ll add the real content later.

First of all, we need to install the React Navigation package for native projects. Then there are a few additional packages we need to install that are compatible with Expo.

npm install @react-navigation/nativeexpo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view

Those packages might seem a bit extensive at first sight but that’s all according to the official documentation. With the latest version of React Navigation, they cleaned and split up many of their own features and made them easier to maintain. Those are the building blocks we will need to build our navigation on.

Let’s check our requirements again to plan what we are going to build. We want a splash page for our logo, a loading page to do some initial calculations and other preparations (rehydrating our store from local storage and such) and then the user will either be asked to login/register or be forwarded directly to the home screen. From there he can change his settings or start the actual game.

My DefaultPage component and folder structure with the new scenes

I went ahead and created 6 empty screen components according to our planned navigation along with a DefaultPage component to add basic styling to all pages. You can download my scenes folder and the new component from Github again or write them yourself.

We will use those blank templates to have something to navigate to and will later on turn those into fully blown and designed app pages.

GitHub Commit: Added DefaultPage and blank screens

And that was when I realised my sloppyness had caused 2 errors… A typo in “src/scenens” and I had forgotten to add PropTypes. I quickly fixed both so MEA CULPA!

Let’s forget about this little faux pas and continue by building our first navigation stack. To do so, we’ll install the stack navigator package first.

npm install @react-navigation/stack

Then we’re going to build a navigator component that will be responsible for displaying the right content depending on the navigation actions triggered by our user. The NavigationContainer will provide us with our navigation context and the StackNavigator is one of a few possible navigators (two others being the TabNavigator and the DrawerNavigator). We can then load our prepared scenes into the StackNavigator.

It’s time to clean up our App.js a bit, remove anything but the Redux store, use enableScreens from “react-native-screens” and include our new Navigation component. We will leave the rest to the navigation from now.

If you check the running app now, you should see nothing but a slightly grey screen with the word “Splash” in the middle. This means our navigator is working and displays the topmost (and only) screen in our navigation stack. To get rid of the white top bar (the default Navigation Header) we need to add one more attribute stack screen.

<Stack.Screen
name="Splash"
component={SceneSplash}
options={
{
headerShown: false }
}

/>

GitHub Commit: Added Navigator

Let’s add more pages

Quickly grab the remaining scenes and add them to the navigation.

Starting with our splash page, we want to show the user our awesome company logo for a few seconds before continuing to the next screen. This means we will trigger the navigation event after a set timer and not as a result of a user action. For that, we’re going to use useEffect and setTimeout. We will skip the SceneAddLoading for now and directly jump to the login.

GitHub Commit: Added more scenes and Splash auto navigation

Storing the Login

For the login, we need to touch our Redux store again. We’ll keep the actual login very simple (read: fake) for now but we still need an action and a reducer for that.

The actions are pretty clear, we’ll need a login and a logout action and the login should be able to store a name.

The reducer needs to handle if the user is logged in and what the user name is. The initial state should be isLoggedIn: false.

Don’t forget to add the user.reducer to our root.reducer so that Redux knows he has to handle our new actions. For our login screen, we’ll throw a few form elements together and connect our component to the store to dispatch the login action and show the name after a successful login. Our input is linked to a useState and we will dispatch that state with our loginUser action.

GitHub Commit: Added user actions and reducer for login

Acting upon the login action

Right now we only store the user name and set him as logged in. If we want to dispatch a navigation action, we need to implement a user middleware that checks the login and triggers the navigation.

This is not a generic core middleware but an app-specific middleware. We will create a new index.js under src/redux/middleware/app/ and import our new user.middleware.js there. This way we can register all future middlewares in one place and still keep everything nice and tidy.

// src/redux/middleware/app/index.js
import userMiddleware from './user.middleware'

export default [
userMiddleware,
]
// src/redux/middleware/index.js
import coreMiddleware from './core'
import appMiddleware from './app'

export default [
...coreMiddleware,
...appMiddleware,
]

This might seem a bit useless right now but if we later on add more middleware, this little pattern will keep things easier to read and scale.

There is one little problem we need to fix before we can implement our middleware though. Redux doesn’t know anything about our navigation as our store provider sits outside of our navigation. To fix this, we will build a small helper that creates a ref to the navigator and we’ll call it RootNavigation.

If we now use this reference in our NavigationContainer, we have created a bridge that we can call from everywhere in our app, our new Redux middleware included.

// src/navigation/index.jsximport { navigationRef } from './RootNavigation'// [...]
const Navigation = () => (
<
NavigationContainer ref={navigationRef}>
<
Stack.Navigator initialRouteName="Splash" screenOptions={{ headerShown: false }}>
// [...]
</
Stack.Navigator>
</
NavigationContainer>
)

export default Navigation

Now that this is done, we can continue with our current login navigation challenge.

In our user middleware, all we need to do is listening for an event of the USER_LOGIN type and fake the actual login check before we pass the login action along and wait a few seconds before triggering the navigation via our new RootNavigation reference.

GitHub Commit: Added user Middleware and navigationRef

Final Tweaks

There’s not much left to do for now but I think this article got long enough already. Time flies when you are having fun, ain’t it so?

From our SceneHome we want to navigate to our still empty Settings and Game scenes. We simply need to access the global prop navigation again and use it to implement simple buttons.

And to get back from our Settings scene, we can make use of another navigation function called goBack().

GitHub Commit: Added Navigation to Home and Settings

Adding the game

I said in the beginning that I will add a small game in the end. If you hoped for TicTacToe I have to disappoint you though. I decided to build a minimal version of RockPaperScissors but went for the enhanced version from Big Bang Theory and added Lizard and Spock as well.

If you want to look at the code, here’s the link to the component. It’s a bit crude, only uses React’s useState and not our redux store but it’s a placeholder anyhow so why bother. We will build more fun and full-blown games in the coming episodes of this series.

GitHub Commit: Added the game

Wrapping up and Coming up next

I think it’s time to call it a day. We’ve implemented a brand new blank Expo-managed React Native app, added our Redux store and Navigation and even included a small game to play.

>> Final Code on GitHub v0.1.2 (with some minor cleanup changes)

Next time we’re going to add some Jazz and Style to our game to make it prettier and somehow visually appealing. Maybe it will even be fun to play with a bit more design and better UI elements but when we’re done with that, we can use this to start with the actual game building. I’ve got a few game concepts lined up, just to tease you a bit, so stay tuned and see you next week.

>> Part 2: Creating an intuitive and invisible UX <<

Things we will build in the following weeks:

  • Copy Clicker | the Idle Incremental Game
  • IF | Interactive Fiction as a Game
  • Match 3 | Block Based Logic Game
  • Hex Builder | Hex based Grid Building Game
  • many more

Some words about me:

If you want to see more of my work and progress, feel free to check out my series on building a React Native Web App with Hooks, Bells, and Whistles with Expo SDK33. I’m also currently working on a new series about building more complex and scalable apps using React/Redux, where I’ll go into details about how and why I do stuff the way I do as well as some articles on my experience in building games for web and mobile with React.

Here are some of my recent topics:
-
Spread & Rest Syntax in Javascript
- clean and simple Redux, explained
- Game Theory behind Incremental Games
- Custom and flexible UI Frames in React Native

And if you feel really supportive right now, you can always support me on patreon, thus allowing me to continue to write tutorials and offer support in the comments section.

I’m a Web / App Developer & father 👨‍👩‍👧 doing freelance and part-time agency work since 2003, 💻 building stuff on the side 🕹 and attending conferences 🎟