Table of Contents
- React Native Expo Template
This template is a starting point for building a React Native app using Expo in STRV.
It provides a foundation for every stage of the development process:
🔧 For the project start and initial development:
- Expo Development build setup
- domain driven folder structure
- Storage service using MMKV
- Font and size scaling utilities
📏 For keeping the code quality high and enforcing code standards:
- basic unit test setup
- Linter and formatter setup
- pre push and pre commit hooks
🚀 For shipping and production maintenance:
- Github Actions for CI/CD
- App config ready for different environments
- Utilities for version check and forced updates in production
⚠️ We don't include any more opinionated solutions such as:
- Styling and theming solution
- State management library
- Image loading library
- E2E testing setup You need to decide what is best for your project and add it to the project. But we provide some recommendations in the Other Recommended Solutions section.
- Clone the repository
- Run
pnpm installto install dependencies - copy
.env.exampleto.envusingcp .env.example .env - run
pnpm ios/androidto start the development server and run the app on your device
To use the full CI/CD pipeline you also need to:
- Setup EAS and EAS credentials
- Setup Github environment
- Setup Slack app for notifications (optional)
- Jira Setup (optional)
- The main benefit is better maintainability as most of the native setup is done through
app.config.tsand community/custom config plugins. Updating Expo SDK mostly assures compatibility with majority of dependencies used, which is a common source of problem when upgrading React Native separately. You can use expo doctor to check for any issues with the setup and update the project accordingly.
- EAS helps with building and app submission. It can create and store all important credentials so that we don't have to distribute them among everyone.
- Default build profiles in
eas.json:dev- this profile will build anexpo-dev-client, meaning that after installing the app, one can change non-native code and see changes reflected in the appstaging- should be distributed for testing, does not have a dev client, meaning it cannot be manipulated. It buildscom.xxx.xxx.stagingapplication which can be distributed through a link or a QR code.
For iOS, we need to add devices for EAS testing (development, staging) via
eas device:create(creates sharable registration link) and confirm the device has been added. It is good to write down your unique device ID to understand what is your device.
production- non-distributable build that should be submitted to Play Store and App Store. Can be tested through Play Store internal testing track or Testflight. It builds the officialcom.xxx.xxxpackage later released to production.
- Environment variables are managed by Expo, use
EXPO_PUBLICprefix to make them accessible in the app app.config.tsdetermines based on the environment a relevant icon, app name and appIdentifier to distinguish individual apps and allow installing side by sideeas.jsoncan setAPP_ENVvariable for each build profile to define environment
babel-plugin-transform-remove-consoleto remove console logs in production
Dev plugins included in the template:
- React Navigation - to see navigation state, history, and params passed to screens
- MMKV - to see MMKV store
Not included but useful:
- We are using @strv/eslint-config-react-native config which is an extension of Expo Universe config with couple of extra rule changes we have found useful.
- We are using mostly standard prettier config.
- Husky is used to run linting and formatting before committing and pushing code.
- Lint Staged is used to run linting and formatting before committing.
User persistence is setup through MMKV which is a synchronized and faster alternative to AsyncStorage.
expo-updates is used to deliver and check for new updates. useOTAUpdates() checks if the update is available on mount and whenever the app goes from background to foreground via useOnForeground() hook. If an update is available, we should show to the user screen/modal/alert that would suggest them to update (reload the app).
⚠️ The OTA updates are used for patches to javascript layer only which is convenient for small bug fixes and UI changes.
To deliver the update through eas update we need to target the right channel from eas.json and manage runtimeVersion in app.config.ts for native layer compatibility otherwise we risk updating incompatible environment resulting in app crashes.
⚠️ runtimeVersion should be changed whenever we change native layer, meaning a new native third party dependency is installed in package.json or we do native config changes in app.config.ts.
❓ A versioning strategy could be to bump minor version for every native change and bump patch version for every javascript change. Then
runtimeVersionwould change from0.1.0to0.2.0, whileversioncould change as follows:0.1.0>0.1.x>0.2.0. This meansruntimeVersion0.1.0is compatible with allversions0.1.x.
⚠️ if we runeas updatelocally, current.envfile is used, so be careful not to publish to production development variables. Better to do it through a github action and setup environment variables as Github Secrets.
Before going to production we should have a forced update functionality in place for cases such as when we introduce a major bug or our backend API is not backward compatible. useStoreUpdate() hook compares minimumSupportedVersion or recommendedVersion with installedAppVersion from app.config.ts. The minimum version should come either from some backend API or third party service such as Firebase Remote Config (good experience). If the app is outdated we should show to the user screen/modal/alert that would suggest them to update (redirect to store listing).
How such a modal could look like is included in the template.
useNetInfo() provides information about current user's network connectivity. In case of isConnected returns false, we can provide a helpful hint (<OfflineMessage/> or full screen) to the user that they are disconnected from the internet so that they don't expect full app functionality. Also provide button to either fetch() the latest connection info or reload the app should they get stuck.
True is the listener is not fully reliable and I see on my Pixel that I am not connected when changing from background to foreground even though I am
React Native allows as default to scale the font significantly which will break the UI of the app. You should expect users to have font scaling up if your target group includes older generation. To allow some level of accessibility but prevent users from breaking the app UI, default scaling of maximum +25% is applied. You can increase it but be sure to control important parts of the application individually to keep UX in place.
To replicate Figma design consistently on majority of mobile screen sizes, we should apply size scaling to UI elements relative to actual device window width/height. This technique is not perfect and implements a subjective scaling factor, but prevents well having too small elements on larger screens. Inspiration: article + library
-
Styling
-
Restyle, Unistyles, Nativewind
Restyle follows a defined theme with strict type safety resulting in consistent and quickly built UI. It is very helpful when a designer defines majority of text variants which can be plugged into the theme and reused super easily. It has also responsive utilities that can make potential transition to a tablet app easier. Unistyles takes a similar approach but is newer and doesn't require special components. Nativewind is a newer library that is based on Tailwind CSS.
-
-
Notifications
-
React Native Firebase Cloud Messaging + Notifee
Both managed by Invertase with latest notification features. Notifee is needed to change Foreground notifications to local ones. Expo-notifications, alternative to both, is also an option but only with native tokens, because using ExpoPushTokens is a strong lock-in, not easily reverted.
-
-
Forms
-
RHF offers many more utilities (and less bugs) than Formik, e.g. to name one, with
setFocus(fieldName)one does not have to setup own refs for inputs. Zod for validation is typescript first and type inference is very reliable and useful.
-
-
Bottom Sheets and Modals
-
Reliable all-in-one solution with good Keyboard Handling options
-
-
Swiping content
-
From Callstack, actively maintained and already supporting the new Architecture, uses native components.
-
-
In-App Purchases
-
Ready-to-go solution from RevenueCat, used on Arnold and Showdown projects.
-
We highly recommend using EAS to manage environments.
We map our environment names to these Expo environment names.
"dev": "development",
"staging": "preview",
"production": "production"
Inside Github Actions we use an action to map the environments automatically. To add new environment variable:
- Either add it through the Expo dashboard
- Or through eas cli:
eas env:create <EXPO_ENVIRONMENT> --name <name> --value <value>
⚠️ All variables must use theEXPO_PUBLICprefix! e.g.,EXPO_PUBLIC_API_URL
You can also pull variables from EAS to your local .env file:
eas env:pull <EXPO_ENVIRONMENT> --path <path-to-env-file>