The recommended starting point for new React Native apps at TELUS Digital. It's an opinionated Expo project with sensible defaults, so every team starts with the same tooling, conventions and quality gates.
Tip
New to React Native? Work through the environment setup guides for iOS and Android before you start. You need Xcode and Android Studio installed.
| Tool | Version | Notes |
|---|---|---|
| macOS | – | iOS builds require a Mac |
| Node | >=22.22.1 |
Use a version manager such as nvm |
| Yarn ⭐ Recommended | 1.22.x |
The supported package manager (over npm). Provided by Corepack, see Package manager |
| Ruby | .ruby-version (3.2.11) |
Use rbenv or rvm |
| Xcode | >=26.4 |
Install an iOS simulator runtime too |
| Android Studio | latest | With an Android SDK and an emulator |
| CocoaPods | – | Don't install it yourself. Bundler installs it from the Gemfile |
yarn install runs scripts/check-tool-versions.sh first and stops with a clear message if Node, Ruby or Xcode is too old.
- On GitHub, click Use this template to create a new repository, then clone it.
- Find & replace
my-app/My Appwith your app's slug and display name. - Find & replace
com.willowtreeapps.myappwith your app id (inapp.jsonand.maestro/home.yml). Maestro flows target the development variant, so keep the.devsuffix in.maestro/(see App variants). - If your project is not open source, update the license.
./scripts/init.sh # clean, install dependencies, generate ios/ and android/, install pods
yarn ios # build and install the development build on an iOS device or simulator
yarn android # same for AndroidThe first build takes a while. After that, yarn start is usually all you need, see Development builds.
- Expo SDK 57 with React Native 0.86 and React 19. The New Architecture is on by default.
- React Compiler is enabled (
experiments.reactCompiler), so you rarely need manualuseMemo/useCallback. - iOS and Android only. Web is intentionally unsupported (ADR 0004).
- TypeScript everywhere, including typed Expo Router routes.
This template uses expo-dev-client instead of Expo Go. A development build is your own app binary with the Expo developer tools built in, so it can include any native module and config plugin. Expo Go can't, so this template doesn't work in Expo Go.
The ios/ and android/ folders are generated from app.json, app.config.ts and config plugins using Continuous Native Generation (expo prebuild). They're git-ignored, so don't edit them by hand (ADR 0003). Change config or add a config plugin instead.
| You changed… | Do this |
|---|---|
| JS / TS code | Nothing. Metro reloads it in the running dev build |
A package with native code, app.json, app.config.ts or a config plugin |
./scripts/prebuild.sh --clean, then yarn ios / yarn android |
app.config.ts derives the app name and id from the APP_VARIANT environment variable, so different builds can be installed side by side:
APP_VARIANT |
App name | App id |
|---|---|---|
unset / development |
My App (Dev) | com.willowtreeapps.myapp.dev |
preview |
My App (Preview) | com.willowtreeapps.myapp.preview |
production |
My App | com.willowtreeapps.myapp |
Switch with ./scripts/switch-variant.sh <development|preview|production>, which regenerates the native projects.
iOS signing uses TELUS Digital's Apple Team ID (appleTeamId in app.json), and owner is set to the willowtreeapps EAS account. Update both if your project uses a client's team or account.
| Area | Library |
|---|---|
| Navigation | Expo Router, file-based routes in src/app/ (ADR 0006) |
| Data fetching | TanStack Query, with the cache persisted to AsyncStorage (ADR 0007) |
| Lists | FlashList |
| Animation | Reanimated, Worklets, Gesture Handler |
| UI | Bottom Sheet, expo-image, react-native-svg (import .svg files directly), DateTimePicker, Slider, WebView |
| Dates | date-fns |
| Open source licenses | react-native-legal generates the third-party license screens for both platforms |
| Native config | expo-build-properties (Android minSdkVersion 33), expo-splash-screen, expo-system-ui, expo-font |
- ESLint (React Native config plus Jest, Testing Library, TanStack Query and unused-import rules) and Prettier. Disabling
react-hooks/exhaustive-depsis blocked. - Husky pre-commit hook runs
yarn lint,yarn formatandyarn tsc --noEmit. - GitHub Actions "PR Checks" workflow runs lint, format check, tests, TypeScript and an iOS JS bundle export on every pull request. Native builds aren't run in CI (ADR 0008).
- VS Code / Cursor recommended extensions and settings in
.vscode/.
- Unit tests: Jest with React Native Testing Library (ADR 0009). Shared helpers are in
src/utils/TestUtils.tsx. - E2E tests: Maestro flows in
.maestro/. They target the development variant (.devapp id) thatyarn ios/yarn androidinstall. Flows launch the app with anisE2Elaunch argument, which turns off LogBox. - Storybook: Storybook for React Native with stories in
.rnstorybook/stories/.yarn start:storybookserves Storybook in place of the app inside your dev build. - React Query DevTools: in development, press
shift + min the Expo CLI to open the@dev-plugins/react-queryinspector.
The template is set up for agentic development with Claude Code, Codex and GitHub Copilot (ADR 0005):
AGENTS.mdholds project rules for agents.CLAUDE.mdis a symlink to it, and.github/copilot-instructions.mdcovers Copilot.- Expo Skills are vendored in
.agents/skills/and pinned inskills-lock.json. - MCP servers for Expo, Figma and GitHub are configured in
.mcp.json(Claude Code) and.codex/config.toml(Codex).
| Command | What it does |
|---|---|
yarn start |
Start Metro for an already-installed dev build |
yarn ios |
Build and run on an iOS device or simulator (you'll be prompted to pick one) |
yarn android |
Build and run on an Android device or emulator |
yarn start:storybook |
Start Metro with Storybook instead of the app |
yarn test |
Run Jest unit tests |
yarn test:e2e |
Run Maestro flows against the installed app |
yarn lint |
Run ESLint on src/ |
yarn format |
Format everything with Prettier |
yarn tsc --noEmit |
Type-check the project |
| Script | What it does |
|---|---|
scripts/init.sh |
Full reset: clean, install dependencies, prebuild both platforms |
scripts/prebuild.sh |
Regenerate native projects and install pods. Options: --platform ios|android, --clean |
scripts/switch-variant.sh |
Clean prebuild for a given app variant |
scripts/clean.sh |
Delete node_modules, vendor, .expo, ios/ and android/ |
scripts/upgrade-dependencies.sh |
Upgrade Expo and all dependencies to the latest compatible versions |
scripts/upgrade-skills.sh |
Update the vendored Expo Skills |
scripts/check-tool-versions.sh |
Check Node, Ruby and Xcode versions (runs automatically before yarn install) |
scripts/compile-plugins.sh |
Compile local Expo modules and config plugins in modules/, if you add any |
src/
app/ Expo Router routes and layouts (file-based routing)
components/ Shared UI components and their tests
hooks/ Custom hooks, e.g. data fetching with TanStack Query
theme/ Theme tokens and colors
utils/ Runtime helpers and test utilities
AppProviders.tsx Root providers (React Query, navigation theme, safe area, gesture handler)
.maestro/ Maestro E2E flows
.rnstorybook/ Storybook config and stories
assets/ App icons, splash screen and images
docs/adr/ Architecture Decision Records: why the template is built this way
scripts/ Setup, build and maintenance scripts
app.json Static Expo config (name, ids, plugins)
app.config.ts Dynamic Expo config (app variants, Storybook flag)
This template uses Yarn (v1) and ships a single yarn.lock with tested, compatible dependency versions (ADR 0010). The exact version is pinned via the packageManager field in package.json, and ./scripts/init.sh enables it through Corepack, so there's no need to install Yarn globally.
# if `yarn` is not on your PATH
corepack enable yarnNote
You can still switch your project to npm if you prefer. Without yarn.lock, npm resolves fresh versions within the ranges in package.json, so you lose the tested lock file. Some current peer dependency ranges also require legacy-peer-deps.
rm yarn.lock
echo "legacy-peer-deps=true" > .npmrc
corepack use npm # updates `packageManager` and generates package-lock.json
./scripts/init.shThen replace yarn commands with their npm equivalents in .husky/pre-commit and .github/workflows/PR Checks.yml (cache: npm, cache-dependency-path: package-lock.json, npm install, npm run …, npx …).
For Expo and React Native packages, always use npx expo install <package> so you get the version that matches the SDK.
Ruby is pinned in .ruby-version, and CocoaPods is installed per project through Bundler and the Gemfile (ADR 0002). You don't need a global CocoaPods install. If you have to use a different Ruby version, update .ruby-version and Gemfile.lock to match.
pod installfails with "CocoaPods could not find compatible versions" after upgrading dependencies.ios/Podfile.lockis stale. Run./scripts/prebuild.sh --clean.npx expo-doctorreports "Failed to find dependency tree … npm explain". Corepack blocksnpmin a Yarn project, and expo-doctor uses it internally. RunCOREPACK_ENABLE_STRICT=0 npx expo-doctor.- The dev build can't connect to Metro. Make sure
yarn startis running and the device is on the same network as your Mac. If you changed native config, rebuild withyarn ios/yarn android. - Something is badly out of sync.
./scripts/init.shresets dependencies and native projects from scratch.
This template is MIT licensed. Most projects built from it aren't open source, so you should:
- delete the
LICENSEfile - remove the
"license"field frompackage.json - add
"private": truetopackage.json
-
Set up distribution. The template doesn't prescribe a build and release pipeline: use EAS, Fastlane or your own CI, whichever fits your project. Whatever you choose, set
APP_VARIANTtodevelopment,previeworproductionfor every build (see App variants). A build without it gets the.devapp id and the "(Dev)" name. With EAS, setenv.APP_VARIANTon each build profile ineas.json, as described in the EAS app variants guide. With Fastlane, check how your lanes define schemes (schemeinbuild_app) and Android build flavors.APP_VARIANTis applied when the native projects are generated, so run./scripts/switch-variant.sh <variant>before building, and add schemes or flavors through a config plugin rather than by editingios/orandroid/. If you use EAS Update, the EAS Update GitHub Action can post QR codes on pull requests. -
Collect code coverage by adding this to
jest.config.js:collectCoverage: true, collectCoverageFrom: [ '**/*.{ts,tsx,js,jsx}', '!**/coverage/**', '!**/node_modules/**', '!**/babel.config.js', '!**/expo-env.d.ts', '!**/.expo/**', ],
This repository is TELUS Digital's fork of jpdriver/react-native-template. To pull in upstream updates, merge the upstream branch (don't squash or re-apply it) so future merges stay conflict-free. Keep these TELUS Digital-specific changes when resolving conflicts:
app.json:owner,ios.appleTeamId, and thecom.willowtreeapps.myappapp id- The extra Expo Skills in
skills-lock.jsonand the localcreate-adrskill - This README and ADR 0010