One of the great things about React Native is the flexibility. You can do just about anything — including organize the project however you want. This is great! But can also be daunting, especially to new developers.
There are numerous ways to organize a project and you can spend a lot of time reading about them and debating the pros and cons of each (none will be perfect). I want to introduce you to one, the one I use. It’s only one way and I’ve found it to work over time and stay simple to understand.
There are a few primary goals I have when organizing a React Native project.
- Maximum Code Reuse — Much like being able to write for both platforms I want be able to reuse as much code as possible between the two platforms. Even if the UI components look different between platforms the business logic behind them should be the same and reused.
- Keep Configuration out of the Code — Settings, colors, routes, etc. are all pieces of code that are used in multiple places throughout the app and can change. I want to make it easy to change these items without worrying about if I changed everyone of them or if I broke anything. These items should live in one easily accessible place.
- Minimize Component State — I want to use stateless functional components, components that are a function of their props, as much as possible. It keeps things simple, I’m a firm subscriber of KISS (Keep it Simple, Stupid).
Understanding the Default Structure
Before diving into our customizations let’s do a quick overview of what you’re getting by default. This will help you understand what we’re working with and what everything means.
- android/— This is the directory where all of the native Android code lives. If you dive in there you’ll find .gradle files, .java files, and .xml files. This is the directory you would open with Android Studio. You’ll rarely have to work in this directory.
- ios/ — Like the android directory this is where all of your native iOS code lives. You’ll find your xcode project in there, .plist files, .h files, .m files, etc. So if you want to open your project in xcode you would open ios/<PROJECTNAME>.xcodeproj_. You’ll rarely have to work in this directory.
- index.ios.js — This is the entry point for your ios app into the React Native code. It’s where you’ll want to register your app (via AppRegistry).
- index.android.js — Same as index.ios.js just for Android. You’ll notice that it’s exactly the same code as the ios one, we’ll work on that in the next section.
- Everything else — You probably won’t have to worry about the other stuff — it’s mostly configuration for React Native and the rest is your standard node files/directories (nodemodules/_ and package.json).
The app/ directory
So to go from the default file structure to a point where we’re completing our goals we’ll need to figure out a single entry point for our entire app, that’s where our app/ directory will come into play. Here’s an idea of where will be going.
── app ├── components ├── config ├── index.js └── screens
app/ directory will be a sibling to the
android/ directory — nothing changes in those. Inside of the
app directory is where all of our app logic will be going. It’s broken up into a handful of directories itself to easily & logically organize code. Let’s cover each one.
This directory aligns with our goals of Maximum Code Reuse and Minimize Component State. In this directory we’ll be writing mostly functional components that can be used in multiple places throughout our app. Here’s an example of what a components/ directory looks like (examples pulled from my React Native Meteor Boilerplate).
. ├── Avatar │ ├── Avatar.js │ ├── index.js │ └── styles.js ├── Button │ ├── Button.js │ ├── index.js │ └── styles.js ├── GenericTextInput │ ├── GenericTextInput.js │ ├── InputWrapper.js │ ├── index.js │ └── styles.js └── Loading ├── Loading.js ├── index.js └── styles.js
A little explanation as to what’s going on here. The index.js files manages what is exported from that directory, it’s the only thing we require from outside of that directory. Avatar.js is the actual component (it’s a functional component) that displays our data. Lastly is the styles.js file — this keeps the styles for the component directory.
Now this may seem overkill for such simple items but I’ve found that as a project grows following this structure keeps things easy to understand and consistent.
Why keep styles outside of the component file? The purpose there is that as you break a larger component into smaller ones (like in GenericTextInput) you’ll reuse a lot of the same styling. And remember, we want to maximize code reuse.
This directory aligns with our goal to Keep Configuration out of the Code. Like I mentioned earlier we’re going to keep anything that is used in multiple places throughout our app in one easy to access (and change) place. Here’s an example, again pulling from my React Native Meteor Boilerplate.
. ├── routes.js ├── settings.js └── styles.js
You can see here that we’ve got a routes.js file which gives us a single place for all of our routes, settings.js file which has info such as our server URL, and finally styles.js file which has global colors in it.
This gives us one easy place to go to to manage various configuration items. It’s also nice for when people who aren’t familiar with the code (such as designers) to jump in and swap out an image.
This isn’t very intimidating and there’s no chance of messing up the logic of your app.
This directory aligns with our goal of Maximum Code Reuse and Cross Platform. We’re just keeping any general functions in here. In reality anything you think you’ll put in the app/lib/ directory can probably be found as an npm package.
This is the bread and butter of the app and aligns with our goal to Maximize Code Reuse and write Cross Platform. Typically an app is made up of a variety of screens (or routes/scenes, depending on your preference). For each screen/route/scene you have in your routes.js file you’re likely going to have a screen file.
These screens are essentially “smart” container component that handle all of the data fetching, interaction management, etc. The actual UI is delegated down to the various presentation components in the components directory. With that being said you should very rarely, if ever, have style information in “screen”. This allows them to stay hyper focused.
Here’s the tree from my routes directory:
. ├── Details.js ├── Home.js ├── Profile.js └── SignIn.js
Finally is the only file in our app directory and that serves as our entry point into our Cross Platform app. It allows us to require a single file from both index.ios.js and index.android.js and show the same thing on both platforms.
One thing that I want to drive home is that, in some way or another, each directory is contributing to writing a Cross Platform app, app/index.js just serves as the starting point. If you want to learn more about sharing code between Android and iOS check out this article.
Expanding on the app/ directory
What I ran through before is the most simple version of this pattern. If you’re building a larger app you very well may be using other technologies as well— such as Redux (which I do).
If that is the case it’s easy to extend this pattern. For the Redux example I add a few more files/directories to my app.
- _app/actions — _I keep my redux actions here.
- _app/reducers/ — _Redux reducers live here.
- app/config/store.js — Setup my Redux store here.
I hope you found this beneficial and that it provided a good understanding into the why behind the decisions I make in my project organization.