Last year I wrote a Tic-Tac-Toe game using React and Material-UI (MUI). Now I want to migrate as much of that app as I can over to React Native so that it can run on a mobile device.

The most difficult part of the game to migrate over is the styling since the MUI component library isn’t compatible with React Native. On the other hand, since the MUI uses JSS for its styling, this might help bring most of the classes for styling over with minimal change needed.

What is React Native (RN)? The documentation says that it is a framework for building true native apps, ones that can be compiled for Android or iOS natively instead of using a web-view wrapper like other native frameworks. For the most part, all you need to know is React and JavaScript to build a mobile app.

The React Native team has done a great job providing many commonly used components in React that will compile to their respective OS’ components. There are times that you will need to build custom native components (bridges) that don’t exist in React Native to get functionality that you need, but for this app we won’t need to do this.

For this migration, I will create a new repository and use GitHub Gists to save the old version of the file, then revise it with the React Native code. This way you can view the gist and look at the changes that were made to it without having to create a new branch in the web Tic Tac Toe source repository.

Setup

NOTE 01: I am doing this migration on a Mac with VS Code and are not sure if it will work the same for a Windows environment.

NOTE 02: I am also going to show screenshots from the iOS Simulator for most of the article. I will, however, show final screenshots of both the iOS Simulator and Android Emulator to demonstrate that the migration works on both platforms.

Follow the setup instructions for your OS on the React Native (RN) Getting Started page. Make sure to follow the, “React Native CLI Quickstart,” tab. In my case I followed the macOS/iOS/Android setup steps.

If you haven’t already done so from the, “Getting Started,” section of the RN documentation, create a new project by running the following command:

# As of this article, the latest version of

# React Native is v0.58 cd to your project directory

# and run: $ react-native init RNTicTacToe # Alternatively, if you do not want to install the

# react-native cli globally, you can run: $ npx -p react-native-cli react-native init RNTicTacToe # Once the project is created cd into it. $ cd RNTicTacToe

From inside the newly created project, update the package.json file with convenience scripts to start the app in iOS or Android.

# File: package.json {

"name": "RNTicTacToe",

...

...

"scripts": {

"android": "react-native run-android",

"ios": "react-native run-ios",

"rn": "react-native"

}

...

...

}

We know that we will need Redux, a navigation framework and an icons package for our app since the web version of the game uses Redux for state management, a navbar for showing a menu with a, “New game,” button in it and Material-Icons for icons in components.

# Add Redux and React Navigation, which is the community

# supported navigation framework for React Native. $ npm install --save redux redux-thunk react-redux prop-types

$ npm install --save react-navigation react-native-gesture-handler

$ npm install --save react-native-vector-icons

Packges:

Let’s link the React Native Vector Icons (vector-icons) to the iOS and Android projects. We can do this automatically by running the link command from the react-native-cli that was used earlier to initialize the project. You can review the installation steps here.

# If you installed the react-native-cli globally, run: $ react-native link react-native-vector-icons # If you used `npx` to initialize the project, run: $ npx -p react-native-cli react-native link react-native-vector-icons # or if you added `rn` to the package.json scripts: $ npm run rn -- link react-native-vector-icons # Do the same for React Native Gesture Handler: $ npm run rn -- link react-native-gesture-handler # You should see 'successful' messages printed in the terminal

At this point, if you run the project, the boilerplate start screen should come up on in the iOS Simulator or Android Emulator.

iOS Simulator, boilerplate start screen.

# Run on the iOS Simulator: $ npm run ios # or for Android: $ npm run android

There is one more thing to finalize before we move onto the migration. I want the project structure to reflect a traditional JavaScript project where the source code is located in the src folder and not at the root of the project.

Let’s update the boilerplate project structure to reflect this.

# Add a folder to the project root called `src`.

# Add an `index.js` file to the `src` folder.

# Move the `app.json` file to the `src` folder.

# Move the `App.js` file at the root into the `src` folder. # You can delete the `__tests__` folder.

# We will be migrating over our tests from the

# web project into the `src` folder. # The project directory should look this this:

We will need to update the new src/index.js file with code that is currently in the project root to make the app work again.

Copy the contents of the root index.js file into the ./src/index.js file.

# File: ./src/index.js // content originally in the ./index.js file

import { AppRegistry } from 'react-native';

import App from './App';

import { name as appName } from './app.json'; AppRegistry.registerComponent(appName, () => App);

Update the index.js file at the root to import the index.js file in the src folder.

# File: ./index.js import './src/index';

Close/Quit the iOS Simulator and make sure the terminal that is running the Metro bundler is stopped.

Metro bundler.

Run npm run ios again to make sure the app runs without errors.

Migration

We will start by migrating over the Redux state store and ducks, then the React container components and finally the presentational components. Afterwards, we will touch up the app by fine-tuning the styles and positioning.

Let’s start by cloning the existing web Tic Tac Toe game and opening that up in a separate VS Code window. I find it best to put the the two Projects side-by-side to make moving code easier.

# Clone from GitHub $ git clone https://github.com/vanister/medium.com.git

Redux state

Since RN is essentially React, with the exception of some components being different than the web version, we should be able to migrate our Redux state over without modifications.

In the web project, open the src/state/ducks/ folder and copy the game folder over to the RNTicTacToe project’s src/state/ folder.

Next, open the web’s src/state/ducks/utils/game.js file and copy its contents into the RNTicTacToe/src/state/game/selectors.js file.

NOTE 03: I decided to move the utils/game.js file into the game state’s selectors.js file since that is what they really are.

then copy the web’s src/state/ducks/index.js and src/state/store.js to the RNTicTacToe/src/state/ folder.

Finally, open each of the copied files and make sure the references are updated accordingly. Most likely they will be the selectors.js , operations.js , and store.js files since they refer to the utils and ducks folder from the web project structure.

RNTicTacToe state folder.

To confirm that we have moved over all of the redux files and updated the references correctly, run npm test in the terminal of the RNTicTacToe project and all of the tests should pass.

Output of `npm test` from `RNTicTacToe` terminal.

Redux store provider

Now that the reducers and operations are working, let’s set up the app to provide the Redux store to components that will use them later in the migration.

Open the web’s src/index.js file and copy its contents into the RNTicTacToe/src/index.js file. Next replace the content of the RNTicTacToe/src/index.js file with the gist below.

NOTE 04: Go to the Gist directly on gist.github.com and select revisions to see the changes between the web code and the React Native code changes.

RNTicTacToe/src/index.js file.

Run the app again, or if the iOS Simulator and Metro Bundler are still running, hit Command+R to refresh the app.

You can also enable Live Reloading by hitting Command+D then choosing, “Enable Live Reload.”

Header and navigation

Before we jump in and start migrating components, we should add an app header bar similar to the one that Material-UI has for our web app. We’re going to use React Navigation to achieve this.

Using React Navigation to create our app header will also give us support for navigation or top level modal presentation, if we ever need it.

Let’s start by creating a folder for our, “screens,” which will also act as our container components in our migration later on.

Add the following files to the screens folder: screens.js , index.js and GameScreen.js .

The screens.js file is where we will keep constants of our screens in the app so we don’t have to rely on magic strings directly in other components that might need it.

// File: src/screens/screens.js const GAME = 'Game'; export default {

GAME

};

The index.js file is where we keep our screen registration code and the centralized exports of common modules in our screens folder.

// File: src/screens/index.js import { createStackNavigator } from 'react-navigation';

import screens from './screens';

import GameScreen from './GameScreen'; const { GAME } = screens; const createAppNavigator = () => {

const appNavigator = createStackNavigator(

{

[GAME]: GameScreen

},

{

initialRouteName: GAME,

defaultNavigationOptions: {

headerTintColor: '#fff',

headerStyle: {

// material blue theme color

backgroundColor: '#3f51b5'

}

}

}

); return appNavigator;

}; export {

createAppNavigator

}; // we export screens here so we can use it in other

// modules with just an import to `./screens`

export default screens;

The GameScreen.js file, for now, can just be a stub for what will be the migrated Game.jsx container component from the web project.

// File: src/screens/GameScreen.js import React, { Component } from 'react';

import {

StatusBar,

StyleSheet,

Text,

View

} from 'react-native'; class GameScreen extends Component {

// part of react-navigation to style the header

static navigationOptions = {

title: 'Tic Tac Toe'

}; render() {

return (

<View style={styles.container}>

<StatusBar barStyle='light-content'></StatusBar>

<Text>Game Screen!</Text>

</View>

);

}

} const styles = StyleSheet.create({

container: {

flex: 1

}

}); export default GameScreen;

Finally, open up the App.js file and update it with the navigation and header code we just wrote from the screens folder.

// File: src/App.js import React from 'react';

import { createAppContainer } from 'react-navigation'; import { createAppNavigator } from './screens'; const appNavigator = createAppNavigator();

const AppContainer = createAppContainer(appNavigator); const App = () => {

return (

<AppContainer></AppContainer>

);

}; export default App;

Run the app again and we should see a screen with a blue header bar, a white body and the text, “Game Screen.”