React native development doesn’t have any fixed project structure like native ones as it’s based on javascript running on the background. A good project structure keeps project clean, easy to understand for members if you are working in a team. Moreover a good practice of organizing keeps you connected with development for long time. In my time I didn’t find any helpful article/reference and moved on focusing on business logic. But due to lack of proper organization of code I had to face many problems followed by team workers.
In this article I am going to show you a simple but convenient structure that we can use for general purpose application development using react native. This is a fruit of my last 2 years experience on react native and how I start a new project now. We are going to talk about starting and organizing a react native project when done from scratch. Also I will provide Github link of sample project structure at the end of the article.
CREATE NEW PROJECT
We will use react native cli here as I prefer it more than expo but the way we will organize can be used for the both. Also I have yarn installed in my device running ubuntu 18.04. I assume you have react-native installed and you know how to run a project. If not please check this facebook link.
Firstly We will create a simple project that will consist of a landing page, a home screen and a profile page. So lets start our new project like
1 2 3 4 5 |
$ npx react-native init MyApp |
After creating the project the structure will look like
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
MyApp ├── android ├── App.js ├── app.json ├── babel.config.js ├── .buckconfig ├── .eslintrc.js ├── .flowconfig ├── .gitattributes ├── .gitignore ├── index.js ├── ios ├── metro.config.js ├── node_modules ├── package.json ├── .prettierrc.js ├── __tests__ ├── .watchmanconfig └── yarn.lock |
CREATING REACT NATIVE PROJECT STRUCTURE
Next we will create a src folder and it’s children in the project folder like below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
src ├── assets │ ├── fonts │ │ ├── OpenSans-Bold.ttf │ │ └── OpenSans-Regular.ttf │ └── images │ └── sample.png ├── components │ └── atoms ├── index.js ├── navigations │ ├── app-navigator.js │ ├── land-navigator.js │ └── index.js ├── scenes │ ├── profile │ │ └── index.js │ ├── home │ │ └── index.js │ └── landing │ └── index.js ├── services └── styles ├── colors.js ├── index.js ├── mixins.js ├── spacing.js └── typography.js |
Easy enough! Huh !! Don’t worry! Github link for the structure is given at the end of the tutorial. Now let me explain the purpose of the structure
We have 6 folders here – assets, components, navigations, screens, services, styles and a file index.js. So let’s see what are they doing here
- assets – contains static resources like fonts, images etc. we will use in the project.
- components – contains granule or compound level items here like atoms(for smallest components like custom button, input etc) or mixed of small components if needed.
- navigations – contains one or more navigation rules for the whole project.
- scenes – consists of screens or the UI that user will interact with.
- services – services like APIs or others that is required for the project is placed here. You can also add an utils folder to keep all your utilities.
- styles – all the stylesheets or colours or typographies required to beautify the application is stored here
- index.js – the start point where we will define our App that brings all modules together here to link and start navigation of the application.
RESOLVING MODULE
Now we will add “babel-plugin-module-resolver” that simplifies import paths by creating directory alias when compiling your code. This plugin allows you to add new “root” directories that contain your modules. It also allows you to setup a custom alias for directories, specific files, or even other npm modules.
1 2 3 4 5 |
$ yarn add -D eslint-import-resolver-babel-module@^5.1.0 eslint-plugin-import@^2.18.2 babel-plugin-module-resolver@^3.2.0 |
So, we have added module resolver dependency. Now create a .bablerc in our project root folder where we will keep our directory aliases like below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
{ "plugins": [ [ "module-resolver", { "cwd": "babelrc", "root": ["./src"], "extensions": [".js", ".ios.js", ".android.js"], "alias": { "_assets": "./src/assets", "_components": "./src/components", "_atoms": "./src/components/atoms", "_navigations": "./src/navigations", "_scenes": "./src/scenes", "_services": "./src/services", "_styles": "./src/styles" } } ] ] } |
Now we can access above directories with their aliases like ‘_scenes/landing’ etc. Likewise we also need to edit our .eslintrc.js in project root folder to avoid lint errors for new aliases
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
module.exports = { root: true, extends: '@react-native-community', plugins: ['import'], settings: { 'import/resolver': { node: { paths: ['src'], alias: { _assets: './src/assets', _components: './src/components', _atoms: './src/components/atoms', _navigations: './src/navigations', _scenes: './src/scenes', _services: './src/services', _styles: './src/styles' }, }, }, }, }; |
Let’s Design
Structure is created. Now we will do some minimal design(just a text in each screen) to distinguish each screens. Copy the following snippets for landing/index.js, home/index.js, profile/index.js to your code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import React from 'react'; import {SafeAreaView, StyleSheet, Text, TouchableHighlight} from 'react-native'; const LandingScreen = ({navigation}) => ( <safeareaview style="{styles.container}"> <text style="{styles.title}">Landing Screen</text> <touchablehighlight style="{styles.homeBtn}" onpress="{()" ==""> navigation.navigate('Home')}> <text style="{styles.btnTitle}">Go Home</text> </touchablehighlight> </safeareaview> ); export default LandingScreen; const styles = StyleSheet.create({ container: { flex:1, padding:50, alignItems:"center", }, title: { marginTop:100, fontSize: 25, fontWeight: 'bold', }, btnTitle: { fontSize: 16, color:"#fff", fontWeight: 'bold', }, homeBtn: { marginTop:100, padding:10, borderRadius:5, backgroundColor:"#3498db" }, }); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import React from 'react'; import {SafeAreaView, StyleSheet, Text} from 'react-native'; const HomeScreen = () => ( <safeareaview style="{styles.container}"> <text style="{styles.title}">Welcome to Home</text> </safeareaview> ); export default HomeScreen; const styles = StyleSheet.create({ container: { flex:1, padding:50, alignItems:"center", }, title: { marginTop:100, fontSize: 25, fontWeight: 'bold', }, }); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import React from 'react'; import {SafeAreaView, StyleSheet, Text} from 'react-native'; const ProfileScreen = () => ( <safeareaview style="{styles.container}"> <text style="{styles.title}">My Profile</text> </safeareaview> ); export default ProfileScreen; const styles = StyleSheet.create({ container: { flex:1, padding:50, alignItems:"center", }, title: { marginTop:100, fontSize: 25, fontWeight: 'bold', }, }); |
Let’s Navigate
Minimal design is completed. Next, we need to add more dependencies for navigation purpose.
1 2 3 4 5 |
$ yarn add react-navigation@^4.0.0 react-navigation-stack@^1.5.3 react-navigation-tabs@^2.4.0 react-native-gesture-handler@^1.4.1 react-native-reanimated@^1.2.0 |
We have added dependencies for navigation, gesture handler, navigation animation, tab navigation. Now let’s add some navigation rules between the screens. Snippets for navigations/land-navigator.js, navigations/app-navigator.js, and navigations/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import {createBottomTabNavigator} from 'react-navigation-tabs'; import HomeScreen from '_scenes/home'; import ProfileScreen from '_scenes/profile'; const TabNavigatorConfig = { initialRouteName: 'Home', header: null, headerMode: 'none', }; const RouteConfigs = { Home: { screen: HomeScreen, }, Profile: { screen: ProfileScreen, }, }; const AppNavigator = createBottomTabNavigator(RouteConfigs, TabNavigatorConfig); export default AppNavigator; |
Above is the App navigator that contains a tab for Home screen and Profile screen.This screen comes after landing page. Next we will create another navigator that will control flow from landing to Home page.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import {createStackNavigator} from 'react-navigation-stack'; import LandingScreen from '_scenes/landing'; const LandingNavConfig = { initialRouteName: 'Landing', header: null, headerMode: 'none', }; const RouteConfigs = { Landing: LandingScreen, }; const LandingNavigator = createStackNavigator(RouteConfigs, LandingNavConfig); export default LandingNavigator; |
After all navigators are added let’s write our navigations/index.js that comprises of all the navigators and defines RootNavigator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import {createAppContainer, createSwitchNavigator} from 'react-navigation'; import LandingNavigator from './land-navigator'; import AppNavigator from './app-navigator'; const RootNavigator = createSwitchNavigator( { Land: LandingNavigator, App: AppNavigator, }, { initialRouteName: 'Land', }, ); export default createAppContainer(RootNavigator); |
Now let’s edit our src/index.js to create App
1 2 3 4 5 6 7 8 9 10 11 12 |
import React from 'react'; import Navigator from '_navigations'; const App = () => <navigator>; export default App; </navigator> |
After editing this we can now remove App.js in the project root folder. But before that we need to point to our src directory for start point. So let’s edit our project root folder’s index.js like below.
1 2 3 4 5 6 7 8 9 |
import {AppRegistry} from 'react-native'; import App from './src'; import {name as appName} from './app.json'; AppRegistry.registerComponent(appName, () => App); |
All set. Now remove the App.js from project’s root folder and run the application.
First start an metro react server on other terminal in project folder
1 2 3 4 5 |
MyApp $ react-native start |
To run in Android, issue the following command from project’s root folder.
1 2 3 4 5 |
$ npx react-native run-android |
And to run in ios use command below
1 2 3 4 5 |
$ npx react-native run-ios |
It is working right! This is a basic project structure. You can use it for almost all general kind of apps. However, You can add as much folder as you need. Don’t forget to add new folder paths to .babelrc and .eslintrc.js to access via aliases.
Download
Here is the Github Link for source code. You can download and copy the src folder to your project directly and run.
Reference : An amazing article by Helder Burato
So by now you are familiar with a decent React Native project structure. In conclusion I would like to say If you like this article share it with your friends. Also I would love to hear comments from you.