DEV Community

Cover image for My React Native Stack After 1 Year
Arthur Dao
Arthur Dao

Posted on

My React Native Stack After 1 Year

For my first post on Dev.to, I want to share my React Native project structure, configurations and some tips. It contains most of the things I've learnt after 1 year of development with React Native, from the creation to the distribution.

I created a Github repository called typescript-react-native-starter that I now use for all my projects.
So I hope it's helpful for other developers who are new with React Native. And you are welcome to make PR :D

Features

I started to use Typescript recently after several projects which made me understand the importance of typed variables. It might take some time to learn at first but it's worth it. You might avoid hours maybe days of debugging thanks to Typescript.

Plus it makes your code self-documenting which is crucial for projects with severals developers.

      import { action } from 'typesafe-actions';
      import * as types from './actionTypes';

      export const myAction = payload => action(types.MY_ACTION_TYPE, payload);
Enter fullscreen mode Exit fullscreen mode
// Before
handleClick()  {...}

<button onClick={ this.handleClick.bind(this) }></button>

// After
@boundMethod
handleClick() {...}

<button onClick={ this.handleClick }></button>
Enter fullscreen mode Exit fullscreen mode

Project Structure

The structure I used is inspired from many sources so you might find this familiar. I like to seperate my files by category except for some special ones like App.tsx, store.ts,...

The publishing folder also contains some useful placeholder images to deploy your app.
For example, in order to deploy your app on Google Play, even for Internal Testing, you would have to add screenshots, feature graphics,... It was ok at first but after several projects, it's kinda annoying so I decided to create some placeholder images for that.

├── __tests__                            // Unit tests
│   ├── App.test.tsx                     // App component's tests
│   ├── components
│   │   └── MyComponent.test.txs
│   └── ...
├── android
├── app.json
├── assets                               // All assets: images, videos, ...
├── index.js
├── ios
├── publishing                           // Icon, screenshots, preview,... for App Store & Play Store
└── src
    ├── App.tsx
    ├── actions                          // Actions
    │   ├── actionTypes.ts               // Action types
    │   └── app.ts                       // appReducer's actions
    ├── components                       // Components
    │   └── MyComponent.tsx
    ├── constants                        // Colors, sizes, routes,...
    │   └── strings.ts                   // i18n
    ├── containers                       // Screens, pages,...
    ├── lib                              // Libraries, services,...
    ├── index.tsx                        // Root component
    ├── reducers                         // Reducers
    │   └── app.ts                       // appReducer
    ├── sagas                            // Redux sagas
    ├── store.ts
    ├── types                            // Type declarations
    │   └── index.d.ts
    └── utils                            // Utilities
Enter fullscreen mode Exit fullscreen mode

Useful tips

This section is for completely random but useful tips, feel free to share yours in the comment or make a PR.

NavigationService

You can navigate without navigation prop by using NavigationService from src/lib/NavigationService.ts

import NavigationService from '../lib/NavigationService';

//...

NavigationService.navigate('ChatScreen', { userName: 'Lucy' });
Enter fullscreen mode Exit fullscreen mode

Cocoapod

When you run react-native link and the linked library has podspec file, then the linking will use Podfile. To disable this feature, remove

# Add new pods below this line
Enter fullscreen mode Exit fullscreen mode

from line 24 in ios/Podfile

Static bundle

The static bundle is built every time you target a physical device, even in Debug. To save time, the bundle generation is disabled in Debug

react-native-screens

You can use react-native-screens with react-navigation in order to improve memory consumption

  • Install and follow steps in Usage with react-navigation (without Expo) from react-native-screens

  • Open ./src/index.tsx and uncomment

// import { useScreens } from 'react-native-screens';
// useScreens();
Enter fullscreen mode Exit fullscreen mode

Responsiveness

  • Avoid as much as you can "absolute" position and hard values (100, 300, 1680,...) especially for big ones.
  • Use flex box and % values instead
  • If you have to use hard values, I have this normalize function for adapting hard values accordingly to the screen's width or height. I might upload it on the repository later:
import { Dimensions, Platform, PixelRatio } from 'react-native';

export const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get(
  'window',
);

// based on iphone X's scale
const wscale = SCREEN_WIDTH / 375;
const hscale = SCREEN_HEIGHT / 812;

export function normalize(size, based = 'width') {
  const newSize = based === 'height' ? size * hscale : size * wscale;
  if (Platform.OS === 'ios') {
    return Math.round(PixelRatio.roundToNearestPixel(newSize));
  } else {
    return Math.round(PixelRatio.roundToNearestPixel(newSize)) - 2;
  }
}
Enter fullscreen mode Exit fullscreen mode

So now I can use:

// iphone X
normalize(100) // = 100

// iphone 5s
normalize(100) // = maybe 80

// You can choose either "width" (default) or "height" depend on cases:
container = {
  width: normalize(100, "width"), // "width" is optional, it's default
  height: normalize(100, "height")
}
Enter fullscreen mode Exit fullscreen mode
  • Before pushing, test your app on 3 differents emulators: iphone5s (small), iphone 8 (medium) and iphone Xs Max (big)

Beta distribution with Fastlane

  # Using RubyGems
  sudo gem install fastlane -NV

  # Alternatively using Homebrew
  brew cask install fastlane
Enter fullscreen mode Exit fullscreen mode

iOS

  • Open your project Xcode workspace and update your app's Bundle Identifier and Team
  • Initialize fastlane
  cd <PROJECT_NAME>/ios
  fastlane init
Enter fullscreen mode Exit fullscreen mode
  • Distribute your app
  fastlane beta
Enter fullscreen mode Exit fullscreen mode

Android

  • Collect your Google Credentials
  • Open your project with Android Studio and update your app's applicationId in build.gradle (Module: app) file
  • Select Generated Signed Bundle / APK... from the Build menu
  • Next then Create new... under Key store path then Next and Finish
  • The first time you deploy your application, you MUST upload it into Google Play Console manually. Google don't allow to use theirs APIs for the first upload.
  • Create your application in the Google Play Console (unlike for iOS Fastlane cannot do that for you)
  • Make sure that these 4 checkmark icons are green

    Recommended order: Pricing & distribution, Content rating, Store listing and App releases

    You can find the required assets for Store listing in the publishing/android folder

  • Initialize fastlane

  cd <PROJECT_NAME>/android
  fastlane init
Enter fullscreen mode Exit fullscreen mode
  • Use the Fastfile from publishing
  cp publishing/android/fastlane/Fastfile android/fastlane
Enter fullscreen mode Exit fullscreen mode
  • Distribute your app
  fastlane beta
Enter fullscreen mode Exit fullscreen mode

There is no official plugin to automatically upgrade android version code (unlike the iOS lane).
Before each deployment, be sure to manually upgrade the versionCode value inside android/app/build.gradle.

More

Apple Store Connect's missing compliance

If you dont' use Fastlane and you don't want to Provide Export Compliance Information at every push , then add this to your Info.plist

<key>ITSAppUsesNonExemptEncryption</key>
<false/>
Enter fullscreen mode Exit fullscreen mode


Note that you might have to set that to <true/> if your app uses encryption

Top comments (11)

Collapse
 
aore profile image
Emmanuel Oreoluwa

How do I make ur react native app stable on different screen size
My react native app on my emulator is okay but when I test it on a smaller screen size the soon doesn't fit the screen

Collapse
 
newbiebr profile image
Arthur Dao • Edited

Hi. You are right, I don't know how I can forgot to talk about responsiveness. I will update that soon on my repository. Here are what I do to handle that:

  • Avoid as much as I can "absolute" position, hard values (100, 300, 1680,...) especially for big ones.
  • Instead, I use flex, and % values
  • I have this normalize function for adapting hard values accordingly to the screen's width or height. I might upload it on the repository later:
import { Dimensions, Platform, PixelRatio } from 'react-native';

export const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get(
  'window',
);

// based on iphone X's scale
const wscale = SCREEN_WIDTH / 375;
const hscale = SCREEN_HEIGHT / 812;

export function normalize(size, based = 'width') {
  const newSize = based === 'height' ? size * hscale : size * wscale;
  if (Platform.OS === 'ios') {
    return Math.round(PixelRatio.roundToNearestPixel(newSize));
  } else {
    return Math.round(PixelRatio.roundToNearestPixel(newSize)) - 2;
  }
}

So now I can use:

// iphone X
normalize(100) // = 100

// iphone 5s
normalize(100) // = maybe 80

// You can choose either "width" (default) or "height" depend on cases:
container = {
  width: normalize(100, "width"), // "width" is optional, it's default
  height: normalize(100, "height")
}
  • Before pushing, I test my work on 3 differents emulators: iphone5s (small), iphone 8 (medium) and iphone Xs Max (big)

Hope this help :D

Collapse
 
onuruludag profile image
Onur Uludağ

Hi Arthur, thanks for the article.

Just wanted to confirm, do we need the Math.round operation in normalize function? Since roundToNearestPixel returns a layout dimension and guarentees that it will be rendered to a non-fractional amount of pixels in the native side, I think we don't need to round once more. Also, what is the reason for 2dip difference in Android?

Thanks a lot.

Collapse
 
dcefram profile image
Daniel Cefram Ramirez

I'd like to also ask as to how do you deal with system font sizes? Or is that something that should be considered while designing the app beforehand?

Collapse
 
newbiebr profile image
Arthur Dao

I use the function normalize() that I mentionned in the post so I don't have much problems with the font sizes.

Collapse
 
dcefram profile image
Daniel Cefram Ramirez

Oh, so that's not just for the actual screen size of the phone, but also for font sizes? TIL

Thread Thread
 
newbiebr profile image
Arthur Dao

Well the function adapt a hard value like 13 depending on the screen sizes for exemple on the iPhone X the fontSize will be 13 but on the iPhone5, the fontSize is 11

Collapse
 
thienlhh profile image
Thien Le

Excellent post, trying to use TS on my next project too!

Collapse
 
roshanadh profile image
Roshan Adhikari

I am getting a 404 on the GitHub link

Collapse
 
newbiebr profile image
Arthur Dao

Ah yes I forgot to update it. Thank you. Here is the link github.com/NewBieBR/typescript-rea...

Collapse
 
roshanadh profile image
Roshan Adhikari

thanks :)