How To Structure a React Native App For Scale
Here’s a sample project that demonstrates the concepts discussed in this article.
Let’s discuss an approach to architecting large React Native applications — a great one i suppose.
Learning and building with React Native(RN) has been an interesting one to say the least, with the fast pace of innovation and changes in the ecosystem (don’t forget, we’re not at version 1.0 yet!).
I started out building breakable toys and soon grew into having to build a large (enterprise scale) application — and then I got stuck. Where to place what? How to connect the various system components? and all those questions related to architecting a solution from the ground up.
Before we get into implementation details, I’ll highlight the tools that influenced this choice of architecture:
- react-navigation — Application navigation
- redux — Application state management
- redux-thunk — Enabling asynchronous dispatching of actions
- jest — Javascript testing
- reselect — Selector library for Redux
- axios — Http Client
- fastlane — Automation tool
How I’d learnt to structure application modules.
The above structure (type-based) had been my go-to for previous light-weight apps, as it is easy to reason about, also app components seemed very visible, so I started out with it. As the scope of the application began to grow, I found myself including more files to the predefined folders and having to make trips across folders(modules) while working on a single feature or fix. In no time, each module became bloated with files. Also, folder navigation, while keeping mental note of implementation flow became a pain. This approach clearly became infeasible.
A better approach:
Because the other files & folders above (some truncated) are usually part of a default react-native init
installation, our focus would be on the src
folder:
fastlane/
This folder, as you might have observed, exists on same level as src
. It contains configuration logic for deployment as well as other release-related tasks. See the fastlane website for more details.
src/
As seen in the referenced screenshot above, let’s discuss the rationale behind this structure
api/
This folder contains logic related to external API communications, it includes:
constants.js
- where all required static values are stored.helper.js
- for storing reusable logic.- individual feature files — Each feature file contains api communication logic for a particular feature.
assets/
Just as the name implies, this houses static files (e.g images) used in the application.
components/
Shared components used across features are placed in this directory. An example of such (as shown above) is the layout
component, which is used to wrap the application components and determine its overall layout.
features/
A major part of this architecture: the feature
folder, consists of a module for each of the application’s feature.
Let’s examining the /explore/
module in more details:
/actions
Like in most react/react-native application. this folder contains the Action Creators for this feature.
/components
Here we place the explore feature’s components and their related styles
/containers
The feature’s redux-related logic is placed here. For this use-case there’s a single container(representing a screen) being exported, so we placed it in the index
file.
Here’s what the /containers/index.js
looks like:
Each feature has a reducer that mutates its own slice of the application state. All reducers are later combined using redux’s combineReducers
function.
/selectors
This might come across as a bit strange to some of us, however this segment of our architecture is influenced by the reselect package, which enables us to efficiently compute derived data from our application’s state.
In this architecture, we grouped the application selectors on a feature-basis so as to make interaction between various aspects of the app easy to reason about. More details on how reselect works can be found here.
/constants.js
This file contains static values used within the feature. An example of what we could store here is ACTION_TYPES
data.
This is another component of our architecture slightly influenced by a react-package
Due to the scale of this application, we wouldn’t be relying on the out-of-the-box navigation with this.props.navigation
, rather we would be be connecting the app’s navigation to the overall application state. This means that our redux-backed store would be aware of the navigation state.
Lets explore the roles of the various directories in this module.
/actions
This contains logic for a bunch of navigation-specific action-creators, thus the actions/index.js
content would mostly look like:
As you might have observed, we’re importing the NavigationActions
object from the react-navigation package to implement native navigation; they provide a really great documentation too!
/containers
This is the where we connect our navigation logic to the application state — using mapStateToProps
and mapDispatchToProps
. The logic of this container component would look somewhat like this:
The entry point of this module is the index.js
file, and here’s what it looks like:
This entry point navigator files serves as the root navigator for the whole application, and is what is imported in the /containers/index.js
file in the previous section.
It aggregates all of the navigators for various scenes in the application and links them up to their entry routes. Besides aggregation, it also routes to individual scenes when appropriate (as seen with the Splash route in the snippet above).
At the top of this code, we see that other scene-based navigation are imported. A scene navigator could like this:
You can have several scene-based navigators as required by your application. what’s key is to import them into the parent navigator and connect to the appropriate route.
/reducers
Because our app’s navigation data now takes a slice of the application state, we would need a reducer to properly update this sliced based on triggered actions. Compared to other reducers in our app, this has a specialised implementation for both initial state and the reducer function:
https://gist.github.com/anosikeosifo/f0df960c177dfb57c4f7ed5e4557704c
reducers/
This is the application-level reducer. Its function is to merge the various feature-level reducers using redux’s combineReucers
function.
Here’s what reducers/index.js
looks like:
styles/
This module holds our application-level styles. which are then referenced in individual components using React Native’s multiple-style syntax shown in the example below:
<View styles={[ globalStyles.wrapper, styles.textWrap ]}> ...
</View>
index.js
This is the application entry file, it wraps the application with the Layout we earlier discussed, and connects the app to the store using the redux’s Provider
higher order component.
It looks like this:
myStore.js
this contains the store-creation logic.
Whoa, This must have been quite a long read!
While this isn’t all-encompassing*, I hope you find it insightful and helpful. I would be glad if you could share your thoughts and opinions on this as well as on how you go about implementing large-scale RN apps.
- In order to focus on the core of the article’s purpose, i avoided going into details on the various
__tests_
modules.
Additional Resources:
Originally published at www.uxenthusiast.com on December 30, 2017.