carlos Published Aug. 18, 2021 · 8 min read

First steps in building an expo app and good practices

A decorative hero image for this page.

The scalability of a project such as an app is a complex task because it is impossible to know all the functionality you'll end up implementing from the beginning of development. Even if you have a plan for what you want to implement, new ideas almost always arise. In today's post we'll talk about a couple of best practices to minimize scalability issues when your Expo project starts growing.

expo

If you just created your first app and don't know how to organize the project, stay tuned as we'll be going over some tips that would have been useful to me when I started. If I'd known these when I started, I could have avoided rewriting a large part of an app when the code grew, since I hadn't done a good job at organizing the code.

Organization

We'll split the project in the following way

.
├── src
│   ├── navigation
│   ├── scenes
│   ├── components
│   │   ├── lib
│   │   └── atoms
└── App.js

The idea is to have a main navigation controller with 3 or 4 screens, which we'll refer to as scenes. Each scene will have several big components such as a carousel (like Instagram's stories) which we'll keep in our lib folder. We we'll try to code general, reusable components as much as posible. Although this last point can get a bit tricky, it will save you a lot of work in the long run.

These big components are themselves made up of smaller components, which we'll call atoms. Examples of these are a text input field or a button. They are repeated everywhere in our application, so it is crucial they are reusable.eutilizables.

To organize these three items we'll create the src folder in the root of our project and a folder for each of them inside src:

  • navigation
  • scenes
  • components
  • atoms
  • lib

Generalization is key

Let's start with the most simple component, a button.

Create a file called index.js inside the src/components/atoms folder. We'll export our reusable components from that file. Create the file Button.js inside that same folder so that the directory structure looks like this:

.
├── src
│   ├── navigation
│   ├── scenes
│   └── components
│       ├── lib
│       └── atoms
│           ├── index.js
│           └── Button.js
└── App.js

Import the required dependencies in Button.js:

import React from 'react';
import { TouchableOpacity, Text } from 'react-native';

We'll use the TouchableOpacity component to create our button. To make it general, we'll provide style and text arguments to our component..

export const Button = ({text, style}) => {

    return(
        <TouchableOpacity style={style}>
            <Text>{text}</Text>
        </TouchableOpacity>
    )
}

To make it available for the rest of our app, we export it from atoms/index.js:

export { Button } from './Button';

Now we can use if from our App.js file after importing it

import { Button } from './src/components/atoms';
An Android Simulator Screen showing a red square in the middle.

We can instantiate a simple square button with a red background and with the text Button

export default function App() {
  return (
    <View style={styles.container}>
      <StatusBar style="auto" />
      <Button 
        style={{ width: 50, height: 50, backgroundColor: 'red' }} 
        text='Button' 
      />
    </View>
  );
}

Now, if we need a rectanuglar button, instead of having to replicate the code in Button.js every time we need to change the style, we can just pass a different value for the style prop. This makes the code much more compact and easier to understand.

An Android Simulator Screen showing a red square in the middle and a yellow rectangle just below.
export default function App() {
  return (
    <View style={styles.container}>
      <StatusBar style="auto" />
      <Button 
        style={{ width: 50, height: 50, backgroundColor: 'red' }} 
        text='Button' 
      />
      <Button 
        style={{ width: 200, height: 50, backgroundColor: 'yellow' }} 
        text='Another Button' 
      />
    </View>
  );
}

A scene can end up having between 500 and 800 lines of code, so legibility is appreciated.

General changes in a single style

Let's imagine that we now want the text to always be placed at the center of the button. It's as easy as adding that style to the Button.js file directly. This will take effect in every Button in your application, and you won't have to go over every single one of them changing the style.

An Android Simulator Screen showing a red square in the middle and a yellow rectangle just below. This time the text inside the red and yellow buttons has been centered.
export const Button = ({text, style}) => {

    return(
        <TouchableOpacity style={{ ...style, ...styles.container }}>
            <Text>{text}</Text>
        </TouchableOpacity>
    )
}

const styles = StyleSheet.create({
    container: {
        alignItems: 'center',
        justifyContent: 'center',
    },
});

This may look like something trivial now, since we only have two buttons. But think if about all the buttons that are present in an app and you won't find having to go over every single one of them that funny.

Configuring Babel

We won't always be writting a file at the root of the project. As we go into deeper file routes, the import statements will have more complicated routes themselves. To save typing, we're going to use Babel to configure aliases.

Add the Babel dependencies by executing the following command in your app project's root:

yarn add babel-preset-expo babel-plugin-module-resolver --dev

Create the file babel.config.js and add:

module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    "plugins": [
      [
        "module-resolver",
        {
          "root": ["./src"],
          "alias": {
            "@atoms": "./src/components/atoms",
          }
        }
      ]
    ]
  };
};

We've added the module-resolver plugin. In its configuration, we specified that the root source is ./src and that the @atoms alias maps to ./src/components/atoms.

Finally, we move the App.js file inside the src folder and we create an index.js file in the project root.

The contents of that new index.js file come from the file node_modules/expo/AppEntry.js. Copy the contents of that file inside index.js and modify the import statement of App so that if matches the following:

import registerRootComponent from 'expo/build/launch/registerRootComponent';

import App from './src/App';

registerRootComponent(App);

Open the file package.json and write index.js under the route main.

Every time we modify the babel.config.js file, it is recommended that you clear the cache. Stop execution in the terminal where you executed expo start and relaunch it with

expo start --clear

Now that the @atoms alias is configured, the import statement in our src/App.js file becomes

import { Button } from '@atoms';

Upload your project to GitHub

To finish, we'll create a GitHub repository where we'll be able to keep track of the changes we're making.

As before, it might look like it is not worth it now, but when you have a version of the app running in production, another one for testing and two or three developers contributing code at the same time you'll see how necessary it is.

Run git init in the projects root and then run git status to see the modifications you've made.

Commit your changes with

git commit -m "Some descriptive message"

It is recommended to make small commits with few changes and descriptive messages, so that you can later search through them effectively.

Open GitHub from your browser and create a new blank repository. Copy the https address from the repo and execute:

git remote add origin [Your url]

Configure origin as the remote where to push commits by default for the master branch with

git push --set-upstream origin master

By doing this, the next time it will be enough to run git push and everything will be configured.

I hope these tips were helpful, if not now, maybe in the future. See you in the next post!

Ready to bring your vision to life?

Get in touch