Blog

Dissecting our React Native Boilerplate

Over the course of the last year, we had the opportunity to work on many mobile apps. We dipped our toes in developing hybrid mobile apps with Monaca and OsenUI, got familiar with Cordova, and learned the pros and cons of creating hybrid apps.

React Native was always there, but it's only over the past year that we got to work with React Native a lot more. We fell in love with it instantly, mainly because of its native look and feel, and the rapid development it offered.

Since new React Native projects were popping in almost every month, we decided to create a React Native boilerplate, so we could kick-start any React Native project as effortlessly as possible.

We had a couple of things in mind:

  • Our React Native boilerplate should be built upon latest and most reliable libraries out there
  • It should include all necessary basic features, such as push notifications and internationalization
  • It must be easy to build upon and expand

To sum it up, the main idea of the boilerplate was to start each project with the best possible React Native stack and set up a good structure on which all the developers, from less to more experienced ones, could build up and make something great and production worthy.

Expo vs. React Native CLI

The first decision that had to be made was the app initialization method, precisely whether we should use the React Native CLI or the Expo client. While choosing the more appropriate method, we had in mind that the initial stage of development is often the most important one.

For instance, while the application is in its prototype stage, it is of utmost importance that the client can see it and give feedback about the direction the app is heading in. The app is often susceptible to changes early on, and a lot of demos take place, which need to be painless and as easy to set up as possible. In the ideal scenario, the client should be able to install the app on a device and get a first-hand experience. Also, our QA team should be able to access and test the app without losing precious time.

Considering the needs mentioned above, we decided to stick with Expo. Development with Expo is more robust and developer friendly, it eases the burden of building binaries for the iTunes Store and Google Play store, keeps Xcode and Android studio out of the way, which makes the start of the project much easier.

All these good things come with a cost of some flexibility, meaning Expo doesn’t support features like Bluetooth and WebRTC, which doesn’t represent a problem since those features are usually not introduced into the app at the very beginning. When the time comes to include those features, we can just eject the app and continue with the development with the help of ExpoKit, or in just plain old React Native way.

Choosing the State Management Tool

Most of the apps we make need a state management tool and choosing the right one was easy.

Redux is almost always a go-to solution when it comes to state management tools. It makes the code easy to read, maintain and debug when necessary. There are a lot of benefits to using Redux, such as being framework agnostic, stable, capable of running on client, server and native environments, etc… Having all these in mind, Redux seemed like a natural choice.

Thunks vs. Sagas

Since Redux was introduced into the boilerplate, we had to think about which middleware is going to be used. Redux middleware is mostly used for logging, crash reporting, talking to an asynchronous API and routing, and most of our apps include all of these. Both of the most popular Redux middleware - redux-saga and redux-thunk seemed like a pretty good choice.

In terms of implementation, Redux Saga and Redux Thunk couldn’t be more different, but in terms of what is achievable using any of them - they’re very similar.

Redux Saga allows us to express application logic as pure functions called sagas. Pure functions are great from the testing standpoint since they are repeatable and predictable. Sagas call and put methods return simple JavaScript objects, which are easily testable and don’t require mocking axios requests. On the other hand, Redux Thunk returns promises, which are more difficult to test.

Also, sagas are implemented through special functions called generator functions introduced in ES6. Generators are functions which can be exited and later re-entered, and most importantly, they are super readable and understandable from the developer point of view.

So even if the Redux Thunk library was initially introduced to our boilerplate, we switched to Redux Saga two days later and decided to stick with it.

Tests

We use Jest for testing our React Native applications. Mocha and Chai work great for vanilla JavaScript, but we need to be sure that our UI components are also covered, which is trickier with Mocha, Chai, Sinon or Enzyme. One of the main advantages of Jest is its speed since Jest runs tests in parallel, and it afterwards provides you with nice and simple coverage information directly in the console. You can always use the --watch option, and run tests while writing the code and not have to sit and wait just to see a test failed so you could go back and fix it.

Error Reporting

We think that error reporting plays a huge role in the application development cycle, not only when it comes to production environment. We like to set up error reporting at the very beginning of the project, just to get that out of the way, and make sure that our app is manually tested AND backed up by an error reporting service to cover all those unpredictable cases, long before the app has reached production.

On most of the projects we work on, including our own VivifyScrum, we use Sentry, and it has shown itself to be a great error reporting tool, so we decided to use it in our React Native boilerplate also, and it has never failed us.

Navigation

Thinking about a navigation library is one of the first things you need to think about when creating a React Native app since you will almost without an exception need it. Choosing a navigation library was easy, since Expo suggests using react-navigation, and it truly is one of the best navigation libraries out there, if not the best one.

React Navigation has great documentation with a lot of examples that you would actually use in practice, covering most common scenarios like authentication flow routing. A particular thing that really stands out is its Navigators, which you can mix and nest, and do basically anything you can imagine with them.

As for the actual navigation implementation, the app is split into three parts:

  1. The authentication stack, which consists of Login, Register and similar screens used as part of the authentication flow
  2. The main navigation stack holding all other application screens, tied to the application logic
  3. The authentication loading screen which is shown while determining if the user is logged in and meanwhile providing a smooth user experience

Structure

The boilerplate structure is pretty straightforward. We keep our components separated from container components ( a.k.a. Screens ), further grouping them by features.

We have a separate folder for services, where we handle the logic for authentication or permissions, for example. All our reducers, sagas and actions are kept in a folder called store, which is our preferred way of using Redux. We keep our constants and utility functions, such as date or currency manipulation functions in separate folders in order to separate them from component logic as much as possible and reuse them in other places if needed.

├── App.js
├── README.md
├── __tests__
│   └── App-test.js
├── app.json
├── assets
│   ├── fonts
│   └── images
├── components
│   ├── __tests__
│   └── shared
├── config
│   └── index.js
├── constants
├── helpers
├── i18n
│   ├── index.js
│   └── locale
│       └── en.json
├── navigation
├── screens
│   ├── auth
│   └── main
├── services
└── store
  ├── actions
  ├── index.js
  ├── reducers
  └── sagas
├── package-lock.json
├── package.json
├── ...

Dev stuff

Some of the unmentioned libraries we use in order to improve the development process are:

  • ESLint
    We strive to write consistent and neat code, and we do so with the help of ESLint. Actual ESLint configuration is not so important, as it is important to be consistent across the codebase.
  • husky
    Setting up a pre-commit hook to be used by the whole team, to lint the code before being pushed, for example, is a must.
  • Prettier
    As mentioned above, in order to keep the code which multiple developers are working on consistent, we use Prettier and never have to worry too much while coding.
  • react-native-dotenv
    Used to save environmental variables.
  • axios
    Our HTTP client of choice. We use it on almost every project since it provides us with everything we need and beyond.

Conclusion

If you’re a company which focuses on a couple of technologies, we strongly recommend creating a boilerplate for each one. Using a boilerplate can do wonders for developer productivity, it can simplify the development process and lift the weight of choosing the right libraries for the job from the developer's shoulders.

If you’re an individual, setting up a boilerplate from scratch can also be fun and teach you a lot of new things about which you probably didn’t think earlier, or you did, but not in such depth.

So, what are you waiting for, go create a boilerplate! 😎