react-native-template-obytes Documentation

A template for your next React Native project: Expo, PNPM, TypeScript, TailwindCSS, Husky, EAS, GitHub Actions, Env Vars, expo-router, react-query,...

RAW Doc

Technical Documentation: obytes/react-native-template-obytes

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`README.md`)

React Native Template Obytes

📱 A template for your next React Native project 🚀, Made with developer experience and performance first: Expo, TypeScript, TailwindCSS, Husky, Lint-Staged, expo-router, react-query, TanStack Form, I18n.

Welcome to the Obytes Mobile Tribe's Expo / React Native Starter Kit!

🚀 Motivation

Our goal with this starter kit was to streamline the process of building React Native apps, both for our own team and for our clients. We wanted to create a resource that would allow us to create high-quality apps faster and with less effort, while ensuring that all of our projects adhere to the same code standards and architectural principles.

The benefits of using this starter kit are numerous. It helps our team easily switch between projects, as we can rely on a consistent foundation of code. It also allows us to focus on the business logic of each project rather than getting bogged down in boilerplate code. And, because it promotes consistency across projects, it makes it easier to maintain and scale our apps, as well as share code between teams.

Overall, our starter kit is designed to facilitate efficient and effective app development, helping us to bring the best possible products to our clients

✍️ Philosophy

When creating this starter kit, we had several guiding principles in mind::

  • 🚀 Production-ready: We wanted to ensure that this starter was ready for real-world use, providing a solid foundation for building production-grade apps.
  • 🥷 Developer experience and productivity: Our focus was on creating a starter that would enhance the developer experience and increase productivity.
  • 🧩 Minimal code and dependencies: We aimed to keep the codebase and dependencies as small as possible.
  • 💪 Well-maintained third-party libraries: We included only well-maintained and reliable third-party libraries, to provide stability and support for our projects.

⭐ Key Features

  • ✅ Latest Expo SDK with Custom Dev Client: Leverage the best of the Expo ecosystem while maintaining full control over your app.
  • 🎉 TypeScript for enhanced code quality and bug prevention through static type checking.
  • 💅 Minimal UI kit built with TailwindCSS, featuring common components essential for your app.
  • ⚙️ Multi-environment build support (Production, Staging, Development) using Expo configuration.
  • 🦊 Husky for Git Hooks: Automate your git hooks and enforce code standards.
  • 💡 Clean project structure with Absolute Imports for easier code navigation and management.
  • 🚫 Lint-staged: Run Eslint and TypeScript checks on Git staged files to maintain code quality.
  • 🗂 VSCode recommended extensions, settings, and snippets for an enhanced developer experience.
  • ☂️ Pre-installed Expo Router with examples for comprehensive app navigation.
  • 💫 Auth flow implementation using Zustand for state management and react-native-mmkv for secure data storage.
  • 🛠 10+ Github Actions workflows for building, releasing, testing, and distributing your app.
  • 🔥 React Query and axios for efficient data fetching and state management.
  • 🧵 Robust form handling with TanStack Form and zod for validation, plus keyboard handling.
  • 🎯 Localization support with i18next, including Eslint for validation.
  • 🧪 Unit testing setup with Jest and React Testing Library.
  • 🔍 E2E testing capabilities with Maestro for comprehensive app testing.

Is this starter for me?

Yes 😀

This starter kit is designed to benefit a wide range of React Native developers, from beginners to experienced professionals. Here's why it might be a good fit for you:

  1. For beginners: It provides a solid foundation with best practices and common solutions, helping you learn industry-standard approaches to React Native development.

  2. For experienced developers: It offers a well-structured, production-ready setup that can save you time and effort in project initialization and configuration.

  3. For teams: It ensures consistency across projects and team members, making it easier to onboard new developers and maintain code quality.

  4. For explorers: Even if you prefer not to use starter kits, this project can serve as a valuable reference. You can explore the codebase, documentation, and architectural decisions to gain insights and potentially adopt specific solutions for your projects.

  5. For learners: The starter kit incorporates up-to-date libraries and patterns, allowing you to familiarize yourself with current best practices in the React Native ecosystem.

  6. For AI-assisted development: This starter kit works well with AI coding tools. It provides a solid structure and best practices that can guide AI-generated code. This helps ensure that AI assistance leads to high-quality, maintainable code that fits well within your project.

Remember, you don't have to use the entire starter kit as-is. Feel free to cherry-pick ideas, configurations, or code snippets that align with your project needs. Whether you're building a new app from scratch or looking to improve your existing development process, this starter kit can provide valuable insights and practical solutions.

Why Expo and not React Native CLI?

We have been using Expo as our main framework since the introduction of Continuous Native Generation (CNG) concept and we are happy with the experience.

I think this question is not valid anymore, especially after the last React conference when the core React native team recommended using Expo for new projects.

"As of today, the only recommended community framework for React Native is Expo. Folks at Expo have been investing in the React Native ecosystem since the early days of React Native and as of today, we believe the developer experience offered by Expo is best in class." React native core team

Still hesitating? Check out this article or this video, maybe this one video too.

🧑‍💻 Stay up to date

We are committed to continually improving our starter kit and providing the best possible resources for building React Native apps. To that end, we regularly add new features and fix any bugs that are discovered.

If you want to stay up to date with the latest developments in our starter kit, you can either watch the repository or hit the "star" button. This will allow you to receive notifications whenever new updates are available.

We value the feedback and contributions of our users, and we encourage you to let us know if you have any suggestions for improving our starter kit. We are always looking for ways to make it even more effective and useful for our community. So, please do not hesitate to reach out and share your thoughts with us.

💎 Libraries used

Contributors

This starter is maintained by Obytes mobile tribe team and we welcome new contributors to join us in improving it. If you are interested in getting involved in the project, please don't hesitate to open an issue or submit a pull request.

In addition to maintaining this starter kit, we are also available to work on custom projects and help you build your dream app. If you are looking for experienced and reliable developers to bring your app vision to life, please visit our website at obytes.com/contact to get in touch with us. We would be happy to discuss your project in more detail and explore how we can help you achieve your goals.

🔥 How to contribute?

Thank you for your interest in contributing to our project. Your involvement is greatly appreciated and we welcome your contributions. Here are some ways you can help us improve this project:

  1. Show your support for the project by giving it a 🌟 on Github. This helps us increase visibility and attract more contributors.
  2. Share your thoughts and ideas with us by opening an issue. If you have any suggestions or feedback about any aspect of the project, we are always eager to hear from you and have a discussion.
  3. If you have any questions about the project, please don't hesitate to ask. Simply open an issue and our team will do our best to provide a helpful and informative response.
  4. If you encounter a bug or typo while using the starter kit or reading the documentation, we would be grateful if you could bring it to our attention. You can open an issue to report the issue, or even better, submit a pull request with a fix.

We value the input and contributions of our community and look forward to working with you to improve this project.

❓ FAQ

If you have any questions about the starter and want answers, please check out the Discussions page.

🔖 License

This project is MIT licensed.


Overview (`cli/README.md`)

React Native Template Obytes

📱 A template for your next React Native project 🚀, Made with developer experience and performance first: Expo,TypeScript,tailwindcss, Husky, Lint-Staged, expo-router, react-query, react-hook-form, I18n.

🚀 Quick start

sh
npx create-obytes-app@latest MyApp

Overview

As a team of experienced developers at Obytes Mobile Tribe, we have spent years refining our approach to building high-quality React Native applications. Our starter kit is based on the best practices and tools that we have found to be most effective in our own projects.

This starter kit has been thoroughly tested and proven successful in multiple projects over the past four years. It is regularly used by our team on a daily basis and has helped us deliver great results for our clients.

While our starter kit is heavily influenced by our own opinions and experiences, we have carefully selected the included solutions to address common challenges and meet the needs of the majority of use cases. We believe it offers a streamlined and efficient approach to building React Native apps, and we are confident that it can help others achieve their project goals as well.

🚀 Motivation

Our goal with this starter kit was to streamline the process of building React Native apps, both for our own team and for our clients. We wanted to create a resource that would allow us to create high-quality apps faster and with less effort, while ensuring that all of our projects adhere to the same code standards and architectural principles.

The benefits of using this starter kit are numerous. It helps our team easily switch between projects, as we can rely on a consistent foundation of code. It also allows us to focus on the business logic of each project rather than getting bogged down in boilerplate code. And, because it promotes consistency across projects, it makes it easier to maintain and scale our apps, as well as share code between teams.

Overall, our starter kit is designed to facilitate efficient and effective app development, helping us to bring the best possible products to our clients

✍️ Philosophy

When creating this starter kit, we had several guiding principles in mind::

  • 🚀 Production-ready: We wanted to ensure that this starter was ready for real-world use, providing a solid foundation for building production-grade apps.
  • 🥷 Developer experience and productivity: Our focus was on creating a starter that would enhance the developer experience and increase productivity.
  • 🧩 Minimal code and dependencies: We aimed to keep the codebase and dependencies as small as possible.
  • 💪 Well-maintained third-party libraries: We included only well-maintained and reliable third-party libraries, to provide stability and support for our projects.

⭐ key Features

  • ✅ The latest version of Expo SDK, along with the Custom Dev client, to give you access to a range of powerful features and tools.
  • 🎉 TypeScript for type checking, to help you catch bugs and improve code quality.
  • 💅 A minimal UI kit built with tailwindcss, which provides a range of pre-defined classes for styling your app.
  • ⚙️ Support for multiple environment builds, including Production, Staging, and Development, using Expo configuration.
  • 🦊 Husky for Git Hooks, to automate your git hooks and enforce code standards.
  • 💡 A clean project structure with Absolute Imports, to make it easier to navigate and manage your code.
  • 🚫 Lint-staged for running linters and TypeScript checks on Git staged files, to ensure that your code is always up to standards.
  • 🗂 VSCode recommended extensions, settings, and snippets to enhance the developer experience.
  • ☂️ Pre-installed Expo Router with examples, to provide a comprehensive navigation solution for your app.
  • 💫 An auth flow with zustand and react-native-mmkv as a storage solution to save sensitive data.
  • 🛠 A simple workflow for building, releasing, and distributing your app using Github action.
  • 🔥 React Query & axios and axios for fetching data, to help you build efficient and performant apps.
  • 🧵 A good approach for handling forms with react-hook-form and zod for validation + keyboard handling.
  • 🎯 Localization with i18next , along with Eslint for validation.

✍️ Documentation

😉 Why Expo?

Expo is a powerful tool for building React Native apps, offering a range of features and benefits that can help developers create high-quality apps more efficiently. One question we often receive from the community is why we choose to use Expo instead of the React Native CLI.

In the past, our team used the React Native CLI for our starter kit. However, we found that using Expo presented several advantages. In particular, the introduction of the Custom dev client feature allowed us to take advantage of the Expo ecosystem and utilize native libraries without the need for ejecting the app. This has greatly simplified our development process and enabled us to focus on the business logic of our projects.

Additionally, we have found that using Expo has made it easier to upgrade our apps to new versions, eliminating the issues we previously encountered when using the React Native CLI.

Overall, we believe that Expo offers numerous benefits for building React Native apps and is a valuable tool for any developer. The real question may be, why not use Expo?

🤔 Is this starter for you?

If you are planning to build a React Native app and are looking for a strong foundation, well-designed architecture, and a positive developer experience, then this starter kit is an excellent resource to consider. It offers a comprehensive set of best practices and tools that have been tested and proven effective in multiple projects.

Even if you are not sure that using a starter kit is the right choice for your project, you can still benefit from this resource. You can explore the starter kit and draw inspiration from the solutions it provides for common challenges faced by React Native developers. This can be a helpful way to discover good practices and find effective solutions for your own app development process.

Overall, whether you choose to use this starter kit as is or simply take some ideas from it, we believe it offers valuable insights and resources for anyone looking to build a high-quality React Native app.

🧑‍💻 Stay up to date

We are committed to continually improving our starter kit and providing the best possible resources for building React Native apps. To that end, we regularly add new features and fix any bugs that are discovered.

If you want to stay up to date with the latest developments in our starter kit, you can either watch the repository or hit the "star" button. This will allow you to receive notifications whenever new updates are available.

We value the feedback and contributions of our users, and we encourage you to let us know if you have any suggestions for improving our starter kit. We are always looking for ways to make it even more effective and useful for our community. So, please do not hesitate to reach out and share your thoughts with us.

💎 Libraries used

Contributors

This starter is maintained by Obytes mobile tribe team and we welcome new contributors to join us in improving it. If you are interested in getting involved in the project, please don't hesitate to open an issue or submit a pull request.

In addition to maintaining this starter kit, we are also available to work on custom projects and help you build your dream app. If you are looking for experienced and reliable developers to bring your app vision to life, please visit our website at obytes.com/contact to get in touch with us. We would be happy to discuss your project in more detail and explore how we can help you achieve your goals.

🔥 How to contribute?

Thank you for your interest in contributing to our project. Your involvement is greatly appreciated and we welcome your contributions. Here are some ways you can help us improve this project:

  1. Show your support for the project by giving it a 🌟 on Github. This helps us increase visibility and attract more contributors.
  2. Share your thoughts and ideas with us by opening an issue. If you have any suggestions or feedback about any aspect of the project, we are always eager to hear from you and have a discussion.
  3. If you have any questions about the project, please don't hesitate to ask. Simply open an issue and our team will do our best to provide a helpful and informative response.
  4. If you encounter a bug or typo while using the starter kit or reading the documentation, we would be grateful if you could bring it to our attention. You can open an issue to report the issue, or even better, submit a pull request with a fix.

We value the input and contributions of our community and look forward to working with you to improve this project.

❓ FAQ

If you have any questions about the starter and want answers, please check out the Discussions page.

🔖 License

This project is MIT licensed.


Overview (`docs/README.md`)

Starlight Starter Kit: Basics

text
npm create astro@latest -- --template starlight


🧑‍🚀 Seasoned astronaut? Delete this file. Have fun!

🚀 Project Structure

Inside of your Astro + Starlight project, you'll see the following folders and files:

text
.
├── public/
├── src/
│   ├── assets/
│   ├── content/
│   │   ├── docs/
│   │   └── config.ts
│   └── env.d.ts
├── astro.config.mjs
├── package.json
└── tsconfig.json

Starlight looks for .md or .mdx files in the src/content/docs/ directory. Each file is exposed as a route based on its file name.

Images can be added to src/assets/ and embedded in Markdown with a relative link.

Static assets, like favicons, can be placed in the public/ directory.

🧞 Commands

All commands are run from the root of the project, from a terminal:

Command Action
npm install Installs dependencies
npm run dev Starts local dev server at localhost:3000
npm run build Build your production site to ./dist/
npm run preview Preview your build locally, before deploying
npm run astro ... Run CLI commands like astro add, astro check
npm run astro -- --help Get help using the Astro CLI

👀 Want to learn more?

Check out Starlight’s docs, read the Astro documentation, or jump into the Astro Discord server.


Src Content Docs Index (`docs/src/content/docs/index.mdx`)


title: Obytes Starter | React Native/Expo starter
template: splash
lastUpdated: false
hero:
title: Perfect React Native App Kickstart 🚀!
tagline: "Your All-in-One Solution for Building Outstanding React Native/Expo Apps. From editor setup to store submission, we've got you covered!"
image:
file: ../../assets/logo.webp
actions:
- text: Get Started
link: /overview
icon: right-arrow
variant: primary
- text: Hire Us
link: https://obytes.com/contact?utm_source=starter.obytes.com
icon: external
variant: secondary


import { Card, CardGrid } from '@astrojs/starlight/components';
import GithubStar from '../../components/GithubStar.astro';
import About from '../../components/about.astro';
import Reviews from '../../components/reviews.astro';

Features

We utilize the most recent Expo SDK and React Native version, along with a custom development client feature, to guarantee that you can install any package and retain full control over your app. The latest version of Expo Router comes pre-installed and includes examples that demonstrate how to implement comprehensive navigation within your app. From running type checks, tests, lint checks, and code formatting to generating a new app version, and building your app using EAS Build, already configured and ready to go! We've included a minimal UI with most of the components you'll need to get started with your project. It's built using Tailwind CSS and is very easy to customize. A complete list of recommended extensions, settings, and snippets for Visual Studio Code (VSCode) that enhance the developer experience and boost productivity. We use TypeScript, Eslint, Prettier, and Husky to ensure that your code is clean and consistent. No more worrying about code style or pushing bad code! We've included a comprehensive set of documentation to help you get started with your project and understand our choices and decisions. Multiple app environments with a robust solution to manage and validate environment variables with Zod. It enables easy switching between different environments throughout the development and deployment processes. We use React Hook Form for form management and Zod for validation, along with a set of inputs for your app. We also include keyboard handling to provide a seamless user experience. We have included a complete authentication flow based on Zustand as a state management library and `react-native-mmkv` as a secure and fast solution for storing tokens and other sensitive data. We manage data fetching using React Query and Axios. These powerful libraries make it easy to fetch, cache, and update data in your React applications. We also include a set of VSCode snippets to make your life easier when creating queries and mutations. We provide a complete setup for unit and end-to-end tests using Jest and React Testing Library, along with Meastro for end-to-end testing. This will assist you in writing tests for your app and ensuring that it functions as expected.

In addition to maintaining this starter kit, we provide expertise in custom projects and app development. If you're looking for experienced and reliable developers to bring your app vision to life, reach out to us. Let's discuss your project and explore how we can help you achieve your goals.

Hire Us


Src Content Docs Overview (`docs/src/content/docs/overview.md`)


title: Overview
head:

  • tag: title
    content: Overview | React Native / Expo Starter

Welcome to the Obytes Mobile Tribe's Expo / React Native Starter Kit!

🚀 Motivation

Our goal with this starter kit was to streamline the process of building React Native apps, both for our own team and for our clients. We wanted to create a resource that would allow us to create high-quality apps faster and with less effort, while ensuring that all of our projects adhere to the same code standards and architectural principles.

The benefits of using this starter kit are numerous. It helps our team easily switch between projects, as we can rely on a consistent foundation of code. It also allows us to focus on the business logic of each project rather than getting bogged down in boilerplate code. And, because it promotes consistency across projects, it makes it easier to maintain and scale our apps, as well as share code between teams.

Overall, our starter kit is designed to facilitate efficient and effective app development, helping us to bring the best possible products to our clients

✍️ Philosophy

When creating this starter kit, we had several guiding principles in mind::

  • 🚀 Production-ready: We wanted to ensure that this starter was ready for real-world use, providing a solid foundation for building production-grade apps.
  • 🥷 Developer experience and productivity: Our focus was on creating a starter that would enhance the developer experience and increase productivity.
  • 🧩 Minimal code and dependencies: We aimed to keep the codebase and dependencies as small as possible.
  • 💪 Well-maintained third-party libraries: We included only well-maintained and reliable third-party libraries, to provide stability and support for our projects.

⭐ Key Features

  • ✅ Latest Expo SDK with Custom Dev Client: Leverage the best of the Expo ecosystem while maintaining full control over your app.
  • 🎉 TypeScript for enhanced code quality and bug prevention through static type checking.
  • 💅 Minimal UI kit built with TailwindCSS, featuring common components essential for your app.
  • ⚙️ Multi-environment build support (Production, Preview, Development) using Expo configuration.
  • 🦊 Husky for Git Hooks: Automate your git hooks and enforce code standards.
  • 💡 Clean project structure with Absolute Imports for easier code navigation and management.
  • 🚫 Lint-staged: Run Eslint and TypeScript checks on Git staged files to maintain code quality.
  • 🗂 VSCode recommended extensions, settings, and snippets for an enhanced developer experience.
  • ☂️ Pre-installed Expo Router with examples for comprehensive app navigation.
  • 💫 Auth flow implementation using Zustand for state management and react-native-mmkv for secure data storage.
  • 🛠 10+ Github Actions workflows for building, releasing, testing, and distributing your app.
  • 🔥 React Query and axios for efficient data fetching and state management.
  • 🧵 Robust form handling with TanStack Form and zod for validation, plus keyboard handling.
  • 🎯 Localization support with i18next, including Eslint for validation.
  • 🧪 Unit testing setup with Jest and React Testing Library.
  • 🔍 E2E testing capabilities with Maestro for comprehensive app testing.

🤔 Is this starter for me?

Yes 😀

This starter kit is designed to benefit a wide range of React Native developers, from beginners to experienced professionals. Here's why it might be a good fit for you:

  1. For beginners: It provides a solid foundation with best practices and common solutions, helping you learn industry-standard approaches to React Native development.

  2. For experienced developers: It offers a well-structured, production-ready setup that can save you time and effort in project initialization and configuration.

  3. For teams: It ensures consistency across projects and team members, making it easier to onboard new developers and maintain code quality.

  4. For explorers: Even if you prefer not to use starter kits, this project can serve as a valuable reference. You can explore the codebase, documentation, and architectural decisions to gain insights and potentially adopt specific solutions for your own projects.

  5. For learners: The starter kit incorporates up-to-date libraries and patterns, offering an opportunity to familiarize yourself with current best practices in the React Native ecosystem.

  6. For AI-assisted development: This starter kit works well with AI coding tools. It provides a solid structure and best practices that can guide AI-generated code. This helps ensure that AI assistance leads to high-quality, maintainable code that fits well within your project.

Remember, you don't have to use the entire starter kit as-is. Feel free to cherry-pick ideas, configurations, or code snippets that align with your project needs. Whether you're building a new app from scratch or looking to improve your existing development process, this starter kit can provide valuable insights and practical solutions.

😉 Why Expo and not React Native CLI?

We have been using Expo as our main framework since the introduction of Continuous Native Generation (CNG) concept and we are happy with the experience.

I think this question is not valid anymore specially after the last React conference when the core react native team recommended using Expo for new projects.

"As of today, the only recommended community framework for React Native is Expo. Folks at Expo have been investing in the React Native ecosystem since the early days of React Native and as of today, we believe the developer experience offered by Expo is best in class." React native core team

Still hesitating? Check out this article or this video, maybe this one video too.

🧑‍💻 Stay up to date

We are committed to continually improving our starter kit and providing the best possible resources for building React Native apps. To that end, we regularly add new features and fix any bugs that are discovered.

If you want to stay up to date with the latest developments in our starter kit, you can either watch the repository or hit the "star" button. This will allow you to receive notifications whenever new updates are available.

We value the feedback and contributions of our users, and we encourage you to let us know if you have any suggestions for improving our starter kit. We are always looking for ways to make it even more effective and useful for our community. So, please do not hesitate to reach out and share your thoughts with us.

💎 Libraries used

Contributors

This starter is maintained by Obytes mobile tribe team and we welcome new contributors to join us in improving it. If you are interested in getting involved in the project, please don't hesitate to open an issue or submit a pull request.

In addition to maintaining this starter kit, we are also available to work on custom projects and help you build your dream app. If you are looking for experienced and reliable developers to bring your app vision to life, please visit our website at obytes.com/contact to get in touch with us. We would be happy to discuss your project in more detail and explore how we can help you achieve your goals.

❓ FAQ

If you have any questions about the starter and want answers, please check out the Discussions page.


Src Content Docs Ci Cd Overview (`docs/src/content/docs/ci-cd/overview.mdx`)


title: Overview
head:

  • tag: title
    content: CI/CD | React Native / Expo Starter

CI/CD stands for Continuous Integration and Continuous Delivery/Deployment. It is a set of practices that help developers automate the process of building, testing, and distributing applications to end-users (such as app stores for mobile apps).

Having a good CI/CD process in place is crucial for any project, as it helps you save time and effort, as well as ensuring that you are delivering a high-quality product to your users. It helps avoid common mistakes and bugs, reduces your time to market, and allows for faster updates to your users.

As most of our projects are hosted on GitHub, we use GitHub Actions and Expo EAS as our CI/CD solution. The starter kit comes with over 10 GitHub Actions workflows:

  • .github/workflows/lint-ts.yml: On PR and new commits on the master branch, run linting and formatting checks.
  • .github/workflows/test.yml: On PR and new commits on the master branch, run unit tests.
  • .github/workflows/type-check.yml: On PR and new commits on the master branch, run type checking.
  • .github/workflows/compress-images.yml: On new commits to the master branch, when adding images, open a pull request (PR) with the compressed images.
  • .github/workflows/expo-doctor.yml: On PR and new commits to the master branch, check whether dependencies are aligned with the Expo SDK version.
  • .github/workflows/new-app-version.yml: A workflow to manually trigger and update the app version, and publish a tag.

You can check the full list of workflows here

Pushing the new release to the stores is a weekly process for our team. To make it easier, we have created a simple process using GitHub Actions and Expo EAS. You can check the full guide here.


Src Content Docs Testing Overview (`docs/src/content/docs/testing/overview.mdx`)


title: Testing Overview
head:

  • tag: title
    content: Testing Overview| React Native / Expo Starter

Testing is an essential part of any software development process. It helps ensure that your code is working as expected and that you don't introduce any bugs into your codebase. As we believe in the importance of testing, we have made it easy to write your tests with the starter template. We include the Jest testing framework and the React Native Testing Library for unit tests, along with mocks for most libraries. We also use Maestro for end-to-end tests.

Testing approaches

It's clear that testing is important, but what should you test? Apparently, and especially for front-end apps (including mobile apps), there is no clear answer. Should I aim for 100% code coverage? Should I test every component?

The answer is it depends. It depends on your app, your team, and your goals. We believe that testing is a trade-off between the time you spend writing tests and the confidence you get from them. You should aim for a good balance between the two.

Aiming for 100% test coverage is not always a good idea and doesn't always make sense. Same with testing views and components that only render a UI. Instead, you should focus on the following:

  • Business logic: Test component and function utilities that contain business logic. Form validation, data manipulation and calculations, etc.

  • Complex components: Test components that contain complex logic. For example, components that contain a lot of conditional rendering, or components that contain a lot of state management logic.

Focus on the above and write unit tests for them. This will help you get the most out of your tests and will assist you in finding bugs early on.

To ensure that your app is functioning as expected, we recommend writing end-to-end tests, as they provide the highest level of confidence and help you test the app as a whole similar to how your users will use it.


Package.Json (`docs/package.json`)

{
"name": "ossified-orbit",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/starlight": "^0.37.6",
"@expressive-code/plugin-collapsible-sections": "^0.33.4",
"@expressive-code/plugin-line-numbers": "^0.33.4",
"@fontsource/ibm-plex-mono": "^5.0.8",
"@fontsource/ibm-plex-serif": "^5.0.8",
"astro": "^5.17.1",
"hast-util-to-html": "^9.0.0",
"starlight-llms-txt": "^0.4.0"
}
}


Src Content Docs Changelog (`docs/src/content/docs/changelog.md`)


title: CHANGELOG
head:

  • tag: title
    content: Obytes Starter ChangeLog | React Native / Expo Starter

For complete changelog, please check the GitHub releases page.


Src Content Docs Faq (`docs/src/content/docs/faq.md`)


title: Frequently asked questions
head:

  • tag: title
    content: FAQ | React Native / Expo Starter

This page is a collection of frequently asked questions about the React Native / Expo Starter.

Is this starter for me?

Yes 😀

This starter kit is designed to benefit a wide range of React Native developers, from beginners to experienced professionals. Here's why it might be a good fit for you:

  1. For beginners: It provides a solid foundation with best practices and common solutions, helping you learn industry-standard approaches to React Native development.

  2. For experienced developers: It offers a well-structured, production-ready setup that can save you time and effort in project initialization and configuration.

  3. For teams: It ensures consistency across projects and team members, making it easier to onboard new developers and maintain code quality.

  4. For explorers: Even if you prefer not to use starter kits, this project can serve as a valuable reference. You can explore the codebase, documentation, and architectural decisions to gain insights and potentially adopt specific solutions for your projects.

  5. For learners: The starter kit incorporates up-to-date libraries and patterns, allowing you to familiarize yourself with current best practices in the React Native ecosystem.

  6. For AI-assisted development: This starter kit works well with AI coding tools. It provides a solid structure and best practices that can guide AI-generated code. This helps ensure that AI assistance leads to high-quality, maintainable code that fits well within your project.

Remember, you don't have to use the entire starter kit as-is. Feel free to cherry-pick ideas, configurations, or code snippets that align with your project needs. Whether you're building a new app from scratch or looking to improve your existing development process, this starter kit can provide valuable insights and practical solutions.

Why Expo and not React Native CLI?

We have been using Expo as our main framework since the introduction of Continuous Native Generation (CNG) concept and we are happy with the experience.

I think this question is not valid anymore, especially after the last React conference when the core React native team recommended using Expo for new projects.

"As of today, the only recommended community framework for React Native is Expo. Folks at Expo have been investing in the React Native ecosystem since the early days of React Native and as of today, we believe the developer experience offered by Expo is best in class." React native core team

Still hesitating? Check out this article or this video, maybe this one video too.

Who is behind the starter kit?

This starter kit has been maintained by the Obytes mobile tribe team since 2021. We actively use it for our internal projects and are excited to share it with the amazing React Native / Expo community. The project is open-source, released under the MIT license, ensuring flexibility for users. Our commitment to this starter kit is ongoing; we will continue to maintain and improve it as long as it remains a core part of our internal development process. We welcome contributions and feedback from the community to help make this starter kit even better.

Can you include X feature or library?

We aim to keep the starter as simple as possible. Based on our experience, we believe the current setup provides a solid foundation for most projects.

The starter is opinionated, reflecting our best practices. If you disagree with some of our choices, that's perfectly fine. The starter is designed to be easily customizable to suit your specific needs.

We often receive requests to provide multiple choices for libraries and tools through a CLI. While this may seem appealing, it would significantly increase complexity and maintenance overhead, which we are trying to avoid.


Src Content Docs How To Contribute (`docs/src/content/docs/how-to-contribute.md`)


title: How to Contribute
head:

  • tag: title
    content: How to Contribute | React Native / Expo Starter

Thank you for your interest in contributing to our project. Your involvement is greatly appreciated and we welcome your contributions. Here are some ways you can help us improve this project:

  1. Show your support for the project by giving it a 🌟 on Github. This helps us increase visibility and attract more contributors.
  2. Share your thoughts and ideas with us by opening an issue. If you have any suggestions or feedback about any aspect of the project, we are always eager to hear from you and have a discussion.
  3. If you have any questions about the project, please don't hesitate to ask. Simply open a new QA discussion and our team will do our best to provide a helpful and informative response.
  4. If you encounter a bug or typo while using the starter kit or reading the documentation, we would be grateful if you could bring it to our attention. You can open an issue to report the issue, or even better, submit a pull request with a fix.

We value the input and contributions of our community and look forward to working with you to improve this project.


Src Content Docs Libraries Recommendation (`docs/src/content/docs/libraries-recommendation.md`)


title: Libraries Recommendation
head:

  • tag: title
    content: Libraries Recommendation | React Native / Expo Starter

The starter kit comes with a set of pre-installed and configured libraries. We recommend using these libraries for your project.

Below, we'll list other libraries we often use in our projects. These aren't included in the starter because:

  1. They're for specific use cases.
  2. They need a lot of setup.

This way, you can add them to your project only if you need them, keeping things simple to start with.

State Management:

The starter kit comes with Zustand out of the box but if your application implements a lot of workflows, you might want to use XState as it's more powerful on managing complex workflows and state machines.

For example, if you have a workflow to create a new card for user and this workflow has a lot of steps and conditions, Zustand might not be the best choice as it's more designed for simple state management and XState is your best choice in this case.

Error Reporting:

  • Sentry: very popular solution for error reporting in the javascript ecosystem and has a great integration with Expo.

Notifications:

There is no solution fit all for notifications, but based on your use case we would recommend one of the following:

Analytics:

Charts:


For sure we are missing some great libraries here, so we count on your contribution to add them in the comments sections below.


Src Content Docs Reviews (`docs/src/content/docs/reviews.md`)


title: What people say about the starter
head:

  • tag: title
    content: Reviews | React Native / Expo Starter

This is a list of reviews from people who have used the starter kit.

Please feel free to add your in the comments section 👇


Src Content Docs Stay Updated (`docs/src/content/docs/stay-updated.md`)


title: Stay Up-to-Date with React Native
head:

  • tag: title
    content: Stay Updated | React Native / Expo Starter

Make sure to check out the links below to stay up-to-date with React Native news.

:::note
We are in favor of reducing the number of news sources you use to avoid feeling overwhelmed. Therefore, we have only included resources that we think are essential and deserve your attention.

:::

Websites

Blogs

Newsletters

Twitter accounts

Open source projects

A list of open-source projects and production-ready apps that you can use to learn and get inspired from.


Src Content Docs Ci Cd App Releasing Process (`docs/src/content/docs/ci-cd/app-releasing-process.mdx`)


title: App releasing process
head:

  • tag: title
    content: App releasing process | React Native / Expo Starter

import CodeBlock from '../../../components/code.astro';

Releasing your app to the App Store and Google Play or even to your QA team for real testing is a very important step in the app development process. Internally, we tend to release a new version of the app to our QA team by the end of each sprint(every week or two weeks). This way, we can get feedback from our team and test the app on real devices.

Doing this in a weekly basis requires automating the process as much as possible to gain time and avoid a lot of manual work.

To make this process as easy as possible, we use Github action and expo eas to build and release our app to the App Store and Google Play.

The Philosophy

The philosophy behind this process is to have a single task that you should trigger to build and distribute the app. This task should be triggered manually by any developer on the team.

The main idea is that we focus on the QA release process and push a new release to the QA team whenever our team pushes a new release to GitHub (at the end of the sprint). Whenever we feel that we are ready to push a new release to the App Store and Google Play, we can trigger a production release manually from the master branch or from the last GitHub release tag that has already been pushed to the QA team.

In conclusion, we have two types of releases:

  • QA release: This release is triggered automatically whenever a new github release is created. This release is used to distribute the app to the QA team for testing.

  • Production release: This release is triggered manually whenever we want to push a new release to the App Store and Google Play and can be build it based on the last GitHub release tag or from the master branch.

:::note
We aim to simplify the process as much as possible. Depending on your requirements, you can take inspiration from this process and modify it to meet your needs.
:::

The Process

The starter comes with a set of tools and tricks that help make the process as easy as possible.

`app-release` npm script

A simple npm script that run np package that helps us to manage the version of the app and push a new tag to GitHub.

json
{
  "scripts": {
    "app-release": "cross-env SKIP_BRANCH_PROTECTION=true np --no-publish --no-cleanup --no-release-draft",
    "version": "pnpm run prebuild && git add ."
  }
}

If you run pnpm run app-release, it will ask you to choose the type of release (major, minor, patch). based on the type of release, it will update the version inside package.json.

As we use the version from package.json as app version inside app-config.ts, we just need to run the prebuild script to update App version inside the ios and android folders. The prebuild script will be executed automatically using a version script inside package.json that will handle the task for us. It will also add the changes to the commit that will be pushed to GitHub and create a new tag with the version number.

So technically, we just need to run pnpm run app-release and it will handle the rest for us. from updating native app version to pushing a new tag to GitHub.

GitHub actions and Expo EAS workflows

The starter comes with a set of GitHub workflows that use EXPO EAS to build and distribute the app to the App Store and Google Play.

  • new-app-version.yml : A workflow that run app-release script in order to update the app version and push a new tag to GitHub.

  • new-github-release.yml : A workflow that is triggered whenever a new tag is pushed to GitHub. It will create a new GitHub release based on the tag name with the correct changelog.

  • eas-build-qa.yml : A workflow that is triggered whenever a new release is created on GitHub. It will build the app using EXPO EAS and based on the config it will distribute.

  • eas-build-prod.yml : A workflow that is triggered manually whenever we want to push a new release to the App Store and Google Play. It will build the app using EXPO EAS and based on the config it will distribute.

In conclusion, when you want to release a new QA version, manually execute new-app-version.yml with the correct release type. After successful execution, new-github-release.yml will be triggered automatically, followed by the automatic execution of eas-build-qa.yml. This will distribute the app to the QA team.

Setup Release Process to your app

To setup the release process to your app, you need to follow these steps:

Run Build Locally

First, make sure to create an Expo account then create a new organization for your project.

When then name of your organization is ready, you need to update the EXPO_ACCOUNT_OWNER variable in env.ts file with the name of your organization.

For QA release:

bash
pnpm run prebuild:preview

The above command will regenerate the iOS and Android folders based on the preview configuration.

Then run the following command to build the app using EAS:

:::caution
You need to be logged in to EAS using eas login command before running this command. check step 1 and 2 from EAS docs for more details.
:::

bash
pnpm run build:preview:ios
pnpm run build:preview:android

The above commands will generate the required credentials for the build and store them in EAS servers so that we can use them later to trigger the build from GitHub actions.

For production release:

bash
pnpm run prebuild:production
pnpm run build:production:ios
pnpm run build:production:android

In case you want to submit the app to the App Store and Google Play, you need to check eas submit configuration and follow the steps from EAS docs.

Setup GitHub Actions

All github workflows are already ready to be used in the starter. You just need to add the required secrets to your GitHub repo:

  • GH_TOKEN: A Github token with access to your repo.

  • EXPO_TOKEN: Expo token to authenticate with EAS. You can get generate yours here

Github action and env variables

For simplicity, we assume that all your environment variables are already added to your .env file and have been pushed to your repository.

If you prefer not to push the .env file (recommended), you need to add all your environment variables to GitHub secrets. Then, use create-envfile action to create the env file on the fly before the prebuild script.

yaml
## .github/workflows/eas-build-prod.yml

- name: Create envfile
  uses: SpicyPizza/[email protected]
  with:
    envkey_DEBUG: false
    envkey_SECRET_KEY: ${{ secrets.PRODUCTION_SECRET_KEY }}
    file_name: .env

- name: ⏱️ EAS Build
  uses: ./.github/actions/eas-build
  with:
    EXPO_PUBLIC_APP_ENV: production
    EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}

This action will create a new .env file with the DEBUG and SECRET_KEY variables you added to the action. so make sure to include all your env variables to the action.

Create new release

For QA release, Go to your GitHub repo actions tab and run new-app-version workflow with the your desired release type.

After successful execution, a new tag will be pushed to master and then new-github-release workflow will be triggered automatically and create a github release based on the tag name with the correct changelog.

After that, eas-build-qa workflow will be triggered automatically and build the app using EAS and distribute it to the QA team.

For production release, Go to your GitHub repo actions tab and run eas-build-prod workflow manually.

text

Src Content Docs Ci Cd Workflows References (`docs/src/content/docs/ci-cd/workflows-references.mdx`)


title: Github Action workflows
head:

  • tag: title
    content: Github Action workflows | A list of all the Github Action workflows that come with the starter and how to use them.

import CodeBlock from '../../../components/code.astro';

The starter comes with over 10 GitHub Actions workflows that help you automate the process of checking, testing, releasing, and much more.

All those workflows are self-documented and contain all details directly inside the workflow file as comments. This approach makes it easier to understand what each workflow does and how to use it without having to go back and forth between the documentation and the workflow file.

This page lists all workflows and actions we use and adds more details on the approach we are using to make those workflows as simple as possible.

All files related to GitHub Actions are located in the .github/workflows and .github/actions folders.

Actions

You can think of actions as functions that are used by workflows to perform a specific task and allow you to reuse the same task in multiple workflows without duplicating the code which made them easier to maintain.

All actions are located in the .github/actions folder, and here is the complete list:

⚙️ Node setup and PNM install

⚙️ Setup JDK && Generate APK

⚙️ EAS Build

Workflows

⚙️ Linting and formatting

⚙️ Type checking

⚙️ Expo Doctor

⚙️ Compress Images

⚙️ Running Tests + Coverage Reports

⚙️ New App Version

⚙️ New Github Release

⚙️ EAS Build Preview

⚙️ EAS Build Prod

⚙️ E2E Test for Android

⚙️ E2E Test for Android using Maestro Cloud

⚙️ E2E Test for Android using EAS Build


Src Content Docs Getting Started Create New App (`docs/src/content/docs/getting-started/create-new-app.md`)


title: Create a new App
head:

  • tag: title
    content: Create a new App | React Native / Expo Starter

Let's create a new React Native project with Obytes starter.

Requirements

First make sure you have the following tools installed on your machine:

Initializing a new project

First make sure you have pnpm installed on your machine, if not you can install it using the following command:

bash
npm install pnpm

Start your project using create-obytes-app command:

bash
npx create-obytes-app@latest MyApp

The command will create an expo app named MyApp and install all the dependencies added by the starter.

:::note
Because we're using the Expo custom dev client to support native dependencies with the starter, The Expo Go app is not an option to consider here. Instead, you need to create the app and install it on your simulator or device to start using it.
:::

:::note
Adhere to Android's specific naming conventions as described in the Android documentation.
The name must satisfy the following rules:

  • It must have at least two segments (one or more dots).
  • Each segment must start with a letter.
  • All characters must be alphanumeric or an underscore [a-zA-Z0-9_].
    :::

Open Project on Cursor or VS Code

Cursor is the recommended editor for this starter, The starter comes with a list of recommended extensions, settings and project snippets that we think will improve your coding experience.

Open the project on Cursor using the following command:

bash
cursor .

When you open the project on Cursor you will see a popup asking you to install the recommended extensions, The easy way is to install all recommended extensions by clicking on Install All button.

To ensure that your code is properly validated and formatted, we highly recommend installing all of the recommended extensions. However, if you're hesitant to install them all, we suggest at least installing the following extensions, as they are essential to our code validation and formatting on file save:

Here is the complete list of recommended extensions:

Running the app

If the installation was successful, the created app should be ready to use, and because we are using the expo custom dev client, you may launch the app on your simulator or device by running the following command:

bash
# Run the app on iOS simulator
pnpm ios

# Run the app on Android simulator
pnpm android

Src Content Docs Getting Started Customize App (`docs/src/content/docs/getting-started/customize-app.mdx`)


title: Customize your App
head:

  • tag: title
    content: Customize your App | React Native / Expo Starter

import Code from '../../../components/code.astro';

The starter is a simple expo app. You just need to edit app.config.ts file to update expo attributes and configuration.

Here is the complete config file :

You can read more about expo configuration here.
If you have any configurations that depend on environment variables, such as API URLs or keys, you can create it in config file following the environment variables guide and import your config to app.config.ts file.

:::note
We included TODO comments in the project to guide you to the areas requiring updates.
:::

Splash screen and app icon

As we are using expo to create the starter, updating the app icon and splash screen is straightforward. You only need to update the app icon and splash screen images inside the assets folder and run expo prebuild to update the app icon and splash screen.

As we are supporting multiple variants for development, preview and production environments you need 3 different icons but the right solution is to use the same icon with badges for each environment.

For more details about the app icon and splash screen, please refer to the expo documentation.

👉 Create a splash screen


Src Content Docs Getting Started Environment Vars Config (`docs/src/content/docs/getting-started/environment-vars-config.mdx`)


title: Environment Variables and Configuration
head:

  • tag: title
    content: Environment Variables and Configuration | React Native / Expo Starter

import Code from '../../../components/code.astro';

Managing environment variables in your project is an essential task, but it can also be challenging. That's why we have included a complete setup for environment variables in this project. This setup uses Expo's native environment variable handling with validation and type-checking using the zod library.

All the code related to environment variables is located in the env.ts file at the project root. This TypeScript file reads environment variables using Expo's default behavior (via process.env.EXPO_PUBLIC_*), defines the zod schema for validation, parses the environment object, and returns the parsed object with proper type-safety.

Key Features

  • Single .env file: No more multiple environment files - one .env file for all environments
  • Expo's native behavior: Uses EXPO_PUBLIC_* prefix convention for client-accessible variables
  • TypeScript: Full type-safety and autocomplete for environment variables
  • Conditional validation: Strict validation before prebuild, warnings during development
  • Config records: Bundle IDs, packages, and schemes defined per environment

Environment Variable Prefixes

Critical distinction:

  • EXPO_PUBLIC_* prefixed variables: Accessible in BOTH app.config.ts AND your src folder (client code)
  • Non-prefixed variables (like SECRET_KEY): ONLY accessible in app.config.ts at build-time, NOT in your client code

This is an Expo convention that ensures secrets never leak to the client-side bundle.

Environment Switching

The template supports three environments controlled by the EXPO_PUBLIC_APP_ENV variable:

  • development: Local development (default)
  • preview: Internal testing / staging
  • production: Production builds

Each environment has its own configuration for bundle IDs, package names, and schemes defined in env.ts.

Adding a new environment variable to the project

To add a new environment variable to the project, follow these steps:

1. Add to the `.env` file

Add the new environment variable to your .env file. Use the EXPO_PUBLIC_ prefix if the variable needs to be accessible in your client code:

bash title=".env"
# Client-accessible variable (available in src folder)
EXPO_PUBLIC_MY_NEW_VAR=my-value

# Build-time only variable (NOT available in src folder)
MY_SECRET_KEY=my-secret

:::note[Important]
Only variables with the EXPO_PUBLIC_ prefix will be accessible in your client code (src folder). Non-prefixed variables are only available at build-time in app.config.ts.
:::

2. Add to the Zod schema in `env.ts`

Update the envSchema in env.ts to include your new variable:

ts title="env.ts"
const envSchema = z.object({
  EXPO_PUBLIC_APP_ENV: z.enum(['development', 'preview', 'production']),
  // ... existing vars

  // Add your new client-accessible variable here
  EXPO_PUBLIC_MY_NEW_VAR: z.string(),
});

:::caution
Do NOT add build-time secrets (non-prefixed vars) to the schema. Access them directly via process.env.MY_SECRET_KEY in app.config.ts if needed.
:::

3. Add to the `_env` object

Add the new variable to the _env object in env.ts:

ts title="env.ts"
const _env: z.infer<typeof envSchema> = {
  // ... existing vars

  // Add your new variable
  EXPO_PUBLIC_MY_NEW_VAR: process.env.EXPO_PUBLIC_MY_NEW_VAR ?? '',
};

4. Run prebuild

Make sure to run pnpm prebuild to load the new values:

bash
pnpm prebuild

5. Use in your code

The new environment variable is now ready to use. Access it in your client code:

ts title="src/lib/client.ts"
import Env from 'env'; // or import Env from '@env';

// Access environment variables
console.log(Env.EXPO_PUBLIC_MY_NEW_VAR);

:::note[Type Conversion]
All environment variables are strings by default. Convert them to the correct type:

ts
// For numbers
EXPO_PUBLIC_VAR_NUMBER: Number(process.env.EXPO_PUBLIC_VAR_NUMBER ?? 0),

// For booleans
EXPO_PUBLIC_VAR_BOOL: process.env.EXPO_PUBLIC_VAR_BOOL === 'true',

:::

Environment Switching

To switch between environments, use the EXPO_PUBLIC_APP_ENV variable:

bash
# Development (default)
pnpm start

# Preview environment
pnpm start:preview

# Production environment
pnpm start:production

For prebuild with strict validation:

bash
# Development
pnpm prebuild:development

# Preview
pnpm prebuild:preview

# Production
pnpm prebuild:production

Validation

The template includes conditional validation:

  • Strict validation: Enabled during prebuild (via STRICT_ENV_VALIDATION=1)

    • Throws errors on invalid or missing variables
    • Ensures production builds have correct configuration
  • Warning-only validation: Enabled during start and development

    • Logs warnings but continues
    • Allows rapid iteration during development

Example error message:

bash
❌ Invalid environment variables: { EXPO_PUBLIC_API_URL: ['Invalid url'] }
❌ Missing variables in .env file for APP_ENV=development
💡 Tip: If you recently updated the .env file, try restarting with -c flag to clear the cache.

How it works

Expo's Native Environment Loading

Expo automatically loads environment variables from the .env file. Variables prefixed with EXPO_PUBLIC_ are exposed to the client-side code through the Metro bundler.

TypeScript Environment File

The env.ts file serves as the single source of truth for environment configuration:

ts title="env.ts"
// 1. Define Zod schema
const envSchema = z.object({
  EXPO_PUBLIC_APP_ENV: z.enum(['development', 'preview', 'production']),
  EXPO_PUBLIC_API_URL: z.string().url(),
  // ... other vars
});

// 2. Define config records per environment
const BUNDLE_IDS = {
  development: 'com.obytes.development',
  preview: 'com.obytes.preview',
  production: 'com.obytes',
} as const;

// 3. Build env object from process.env
const _env: z.infer<typeof envSchema> = {
  EXPO_PUBLIC_APP_ENV: process.env.EXPO_PUBLIC_APP_ENV ?? 'development',
  EXPO_PUBLIC_API_URL: process.env.EXPO_PUBLIC_API_URL ?? '',
  // ...
};

// 4. Validate conditionally
const Env = STRICT_ENV_VALIDATION ? getValidatedEnv(_env) : _env;

// 5. Export for use
export default Env;

App Configuration

The app.config.ts file imports the environment using the tsx module loader:

ts title="app.config.ts"
import 'tsx/cjs'; // Required to import TypeScript files
import Env from './env';

export default ({ config }: ConfigContext): ExpoConfig => ({
  name: Env.EXPO_PUBLIC_NAME,
  bundleIdentifier: Env.EXPO_PUBLIC_BUNDLE_ID,
  // ... rest of config

  extra: {
    eas: {
      projectId: EAS_PROJECT_ID,
    },
  },
});

:::note[Environment Variable Access]
Client-side environment variables with the EXPO_PUBLIC_* prefix are NOT passed through the extra field. They are automatically bundled by Metro and accessible directly in your source code via import Env from 'env'.
:::

:::note[TSX Dependency]
The tsx package must be installed as a dev dependency to allow importing TypeScript files in app.config.ts:

bash
pnpm add -D tsx

:::

Client-Side Access

Client code accesses environment variables through the @env alias:

ts title="src/lib/api/client.tsx"
import Env from 'env'; // or import Env from '@env';

export const client = axios.create({
  baseURL: Env.EXPO_PUBLIC_API_URL, // Type-safe access
});

The @env alias is configured in tsconfig.json to point to env.ts at the project root.

Build-Time Secrets

For secrets that should never be exposed to the client (like API keys for build-time operations):

  1. Add them to .env WITHOUT the EXPO_PUBLIC_ prefix
  2. Do NOT add them to the Zod schema in env.ts
  3. Access them directly in app.config.ts:
ts title="app.config.ts"
export default ({ config }: ConfigContext): ExpoConfig => ({
  // ...
  hooks: {
    postPublish: [
      {
        file: 'sentry-expo/upload-sourcemaps',
        config: {
          authToken: process.env.SENTRY_AUTH_TOKEN, // Build-time only
        },
      },
    ],
  },
});

CI/CD Considerations

When working with CI/CD pipelines:

  1. Set EXPO_PUBLIC_APP_ENV to the target environment
  2. Set STRICT_ENV_VALIDATION=1 for prebuild/build steps
  3. Provide all required EXPO_PUBLIC_* variables as secrets

Example GitHub Actions:

yaml
env:
  EXPO_PUBLIC_APP_ENV: production
  EXPO_PUBLIC_API_URL: ${{ secrets.API_URL }}
  STRICT_ENV_VALIDATION: 1

Migration from Old Setup

If you're migrating from the old dotenv-based setup:

  1. Rename .env.development to .env
  2. Add EXPO_PUBLIC_ prefix to all client-accessible variables
  3. Remove .env.staging and .env.production files
  4. Update APP_ENV to EXPO_PUBLIC_APP_ENV in scripts
  5. Replace "staging" with "preview" throughout

See the migration guide for detailed instructions.


Src Content Docs Getting Started Project Structure (`docs/src/content/docs/getting-started/project-structure.mdx`)


title: Project Structure
head:

  • tag: title
    content: Project Structure | React Native / Expo Starter

import { FileTree } from '@astrojs/starlight/components';
import Code from '../../../components/code.astro';

If you open the new project in VSCode you will see the following structure:

  • features ## feature-oriented modules
    • auth/
      • login-screen.tsx
      • use-auth-store.tsx
      • components/
        • login-form.tsx
        • login-form.test.tsx
    • feed/
      • feed-screen.tsx
      • post-detail-screen.tsx
      • add-post-screen.tsx
      • api.ts
      • components/
        • post-card.tsx
    • settings/
      • settings-screen.tsx
      • components/
        • settings-item.tsx
        • settings-container.tsx
        • language-item.tsx
        • theme-item.tsx
    • onboarding/
      • onboarding-screen.tsx
      • components/
        • cover.tsx
    • style-demo/
      • style-screen.tsx
      • components/
        • typography-demo.tsx
        • colors-demo.tsx
        • buttons-demo.tsx
        • inputs-demo.tsx
        • title.tsx
  • app ## app routes and layouts (Expo Router)
    • (app)/
      • _layout.tsx
      • index.tsx (re-exports FeedScreen)
      • settings.tsx (re-exports SettingsScreen)
      • style.tsx (re-exports StyleScreen)
    • feed/
      • [id].tsx (re-exports PostDetailScreen)
      • add-post.tsx (re-exports AddPostScreen)
    • _layout.tsx
    • login.tsx (re-exports LoginScreen)
    • onboarding.tsx (re-exports OnboardingScreen)
  • components ## design system and shared UI components
    • ui/
      • button.tsx
      • checkbox.tsx
      • colors.js
      • focus-aware-status-bar.tsx
      • form-utils.ts
      • icons/
      • image.tsx
      • input.tsx
      • list.tsx
      • modal.tsx
      • progress-bar.tsx
      • select.tsx
      • text.tsx
      • use-theme-config.tsx
      • utils.tsx
  • lib ## core infrastructure and utilities
    • api/
      • index.ts
      • client.tsx
      • provider.tsx
      • utils.tsx (includes PaginateQuery type)
    • auth/
      • utils.tsx (token storage utilities)
    • hooks/
      • use-is-first-time.tsx
      • use-selected-theme.tsx
    • i18n/
      • index.tsx
      • resources.ts
      • types.ts
      • utils.tsx
    • storage.tsx
    • test-utils.tsx
    • utils.ts
  • translations ## translation resources files
    • ar.json
    • en.json

Architecture Overview

The project follows a feature-oriented architecture where each feature is self-contained with its screens, components, API calls, and state management. This approach promotes:

  • Feature isolation: Each feature has all related code in one place
  • Better scalability: Easy to add new features without affecting existing ones
  • Improved maintainability: Clear boundaries between features
  • Team collaboration: Multiple developers can work on different features simultaneously

Key Folders

`features/`

This is where all feature-specific code lives. Each feature folder follows a consistent pattern:

  • Screens: Named with -screen.tsx suffix, placed in the feature root
  • components/: Feature-specific components that are only used within this feature
  • api.ts: Feature-specific API calls and React Query hooks (optional)
  • use-*-store.tsx: Feature-specific state management with Zustand (optional)

Example: The auth feature contains the login screen, login form component, and auth store all in one place.

Convention:

  • ✅ Screens use -screen.tsx suffix
  • ✅ Only components/ subfolder is allowed
  • ✅ Use single files (api.ts) not folders
  • ❌ No index.ts barrel exports (prevents fast refresh issues)

`app/`

This folder contains the Expo Router file-based routing structure. Routes are thin re-export layers that import screens from features:

tsx
// app/login.tsx
export { LoginScreen as default } from '@/features/auth/login-screen';

This keeps routing configuration separate from feature logic while maintaining Expo Router's file-based routing benefits.

`components/ui/`

This is the design system containing reusable UI primitives and shared utilities:

  • UI components (Button, Input, Text, etc.)
  • Form utilities (form-utils.ts)
  • Theme configuration (use-theme-config.tsx)
  • Icon components

When to promote a component here: When it's used in 2+ features and has no feature-specific logic.

`lib/`

Core infrastructure and cross-cutting concerns:

  • api/: Shared API client, React Query provider, and utilities
    • Includes PaginateQuery type used across features
    • Axios client configuration
    • API helper functions
  • auth/: Low-level token storage utilities (getToken, setToken, removeToken)
  • hooks/: Global hooks (useIsFirstTime, useSelectedTheme)
  • i18n/: Internationalization setup and utilities
  • Core utilities: Storage abstraction, environment config, test utilities

What stays here: Infrastructure that features depend on but don't own.

`translations/`

Contains i18n resource files. We recommend using translation files even if you only support one language initially, as it:

  • Makes future multi-language support easier
  • Centralizes all user-facing strings
  • Facilitates copy changes without code modifications

Importing Files

We use absolute imports with direct file paths (no barrel exports) to avoid fast refresh issues:

tsx
// ✅ CORRECT - Direct imports
import { Button, Input, Text } from '@/components/ui';
import { LoginScreen } from '@/features/auth/login-screen';
import { usePosts } from '@/features/feed/api';
import { translate } from '@/lib/i18n';
import { client } from '@/lib/api';

// ❌ WRONG - Barrel exports (causes fast refresh issues)
import { LoginScreen } from '@/features/auth';
import { usePosts } from '@/features/feed';

Import Patterns

Within the same feature (use relative imports):

tsx
import { LoginForm } from './components/login-form';
import { useAuthStore } from './use-auth-store';

Cross-feature imports (use absolute with full path):

tsx
import { useAuthStore } from '@/features/auth/use-auth-store';
import { PostCard } from '@/features/feed/components/post-card';

From design system:

tsx
import { Button, Input, View } from '@/components/ui';
import { getFieldError } from '@/components/ui/form-utils';
import { useThemeConfig } from '@/components/ui/use-theme-config';

From lib:

tsx
import { translate } from '@/lib/i18n';
import { useIsFirstTime } from '@/lib/hooks/use-is-first-time';
import { client, APIProvider } from '@/lib/api';

Configuration

This absolute import pattern is enabled through Babel module resolver and TypeScript path mapping:

⚙️ Babel configuration

⚙️ Typescript Config

Adding a New Feature

To add a new feature, follow this template:

bash
# Create feature structure
mkdir -p src/features/my-feature/components

# Create screen
touch src/features/my-feature/my-feature-screen.tsx

# Create route re-export
echo "export { MyFeatureScreen as default } from '@/features/my-feature/my-feature-screen';" > src/app/my-feature.tsx

For features with API:

bash
mkdir -p src/features/my-feature/components
touch src/features/my-feature/my-feature-screen.tsx
touch src/features/my-feature/api.ts
touch src/features/my-feature/components/my-component.tsx

:::note
This structure is intentionally designed for scalability and maintainability. While you can modify it to fit your needs, we recommend following these patterns as they:

  • Prevent circular dependencies
  • Enable fast refresh without issues
  • Make features easy to locate and understand
  • Support team collaboration
    :::

Src Content Docs Getting Started Rules And Conventions (`docs/src/content/docs/getting-started/rules-and-conventions.mdx`)


title: Rules and Conventions
head:

  • tag: title
    content: Rules and Conventions | React Native / Expo Starter

import Code from '../../../components/code.astro';

In order to enforce a consistent code style and avoid common issues in the codebase, we have a set of rules and conventions that we follow and enforce through the starter.

Typescript

This starter uses TypeScript to provide type safety and avoid common bugs in the codebase. The project configuration is based on Expo config with some updates to support absolute imports.

If you are not familiar with Typescript, you can check this article to learn more about it : Typescript for React Developers

You can find more information about it here.

Naming

We follow kabab-case for naming files and folders as we think it's the most readable and consistent way to name files and folders in large projects and it's the most common way to name files and folders in the react native community.

Example of kabab-case naming: my-component.tsx

For naming variables, functions, classes, interfaces, and enums, we follow camelCase as it's the most common way to name variables in the React community. It is enforced by the linter, as you cannot create a function component without using camelCase.

Linting

Using a linter is a must in any JavaScript project. For starters, we are using ESLint with @antfu/eslint-config and some custom rules to ensure that we are following the rules and conventions related to file naming, Tailwind CSS classes, TypeScript types, import order, internationalization files, and more. This config also handles code formatting, so Prettier is no longer needed.

Here is the complete ESLint configuration file:

Git Hooks with Husky

The starter comes with a set of git hooks that help us to enforce rules and conventions. Those hooks are configured using Husky. and here is the complete list of the hooks:

pre-commit

As the name suggest, this hook will run before each commit and it will make sure you are not committing directly on the main branch and it will run the linter and typescript checking on the staged files.

post-merge

As the name suggest, this hook will run after each merge and it will check if there is any changed in pnpm-lock.yaml and if there is any, it will run pnpm install to make sure the dependencies are up to date.

commit-msg

This hook will check if the commit message is following the conventional commit format. If it's not, the commit will be aborted and will show you what going wrong with your commit message.

We are using commitlint to check if the commit message is following the conventional commit format.

In general, your commit message should follow this format:

bash
type(scope?): subject  #scope is optional; multiple scopes are supported (current delimiter options: "/", "\" and ",")

Real world examples can look like this:

txt
fix(ui): fix input width
feat(ui): add button variants
feat(api): add usePost query hook

type should be one of the following: build, chore, ci ,docs,feat,fix, perf, refactor, revert, style or test.

🔗 Resources


Src Content Docs Guides Authentication (`docs/src/content/docs/guides/authentication.mdx`)


title: Authentication
head:

  • tag: title
    content: Authentication | React Native / Expo Starter

import CodeBlock from '../../../components/code.astro';

Most of the applications we had the chance to work on required some sort of authentication and having a bad approach to do it can lead to a lot of problems. The starter kit comes with the basics and mostly what you need to start with authentication in your application.

As Authentication is global to the application, we end up using Zustand to manage the authentication state of the application.

Zustand is a state management library that is very simple to use and highly performant. It is also easy to integrate with React and works better than a simple context API, as it offers additional features such as selectors to prevent unnecessary re-renders.

Zustand works very well with TypeScript and can be easily used outside the React tree, which means more flexibility.

Authentication Store

As mentioned earlier, we use Zustand to manage the authentication state of the application. The authentication store is located in src/features/auth/use-auth-store.tsx and is utilized for managing the authentication state of the application.

The store is composed of 2 states and 3 actions:

  • status: The status of the authentication. It can be idle, signOut or signIn.

    • idle: Still not sure if the user is authenticated or not (when we are fetching tokens from the storage)
    • signOut: The user is not authenticated
    • signIn: The user is authenticated
  • useToken: The token of the user. It is used to authenticate the user to the API. It is stored in the storage of the device and we use it to hydrate the authentication status state when the application is started.

    For the Demo app useToken is a simple object that contains the accessToken and the refreshToken. You can add more fields if you update the TokenType type in src/lib/auth/utils.ts.

  • signIn: The function performs user sign-in. It accepts a token as a parameter, sets the token state, stores it locally, and updates the status to signIn.

  • signOut: The action to sign out the user. It sets the token state to null and removes it from the storage as well as setting the status to signOut.

  • hydrate: The action to hydrate the authentication status state. We call this action when the application is started to check if the user is authenticated or not. It fetches the token from the storage and sets the status to signIn if the token is not null or signOut if the token is null.

Use Authentication store

You only need to import the store directly from the auth feature and use it in your component or even call store actions from outside React.

tsx
import { useAuthStore as useAuth, hydrateAuth } from '@/features/auth/use-auth-store';

hydrateAuth(); // call this when the application is started to check if the user is authenticated or not

const App = () => {
  const status = useAuth.use.status();
  const signOut = useAuth.use.signOut();

  return (
    <View>
      <Text>{status}</Text>
      <Button title="Sign Out" onPress={signOut} />
    </View>
  );
};

Use Case

Let's imagine we have a simple application that has a login navigator and a home navigator. The home navigator is only accessible if the user is authenticated, while the login navigator is accessible if the user is not authenticated.

In this case, you only need to retrieve the status from the authentication store and display the appropriate navigation based on the status.


Src Content Docs Guides Data Fetching (`docs/src/content/docs/guides/data-fetching.mdx`)


title: Data Fetching
head:

  • tag: title
    content: Data Fetching | React Native / Expo Starter

import CodeBlock from '../../../components/code.astro';

From the beginning of React, the community has been building a rich ecosystem of tools and libraries that help you build your applications. One of the most important parts of this ecosystem is the ability to fetch data from a server and display it in your application.

While there are numerous options available for fetching data in React and React Native, the community has recently been gravitating towards adopting React Query as the go-to solution. The reason for this trend is due to React Query's simplicity, flexibility, and tone of features that it provides out of the box.

What is react-query?

React-query It is a very powerful library that helps you manage your data fetching, caching, invalidating, and even implementing optimistic UI in a very simple way.

react-query offers hooks that simplify the process of fetching and updating data in your app. You can use pre-built hooks like useQuery and useMutation, or create your own custom hooks based on them. This makes it easier for developers to manage data and build more efficient applications.

Using react-query-kit & axios

As the starter is designed to save you time and effort, we already have installed react-query and Axios and configured them for you.

The src/lib/api/ folder contains the shared API infrastructure including the Axios client, queryClient, and several utility functions specifically designed to work with react-query. Each feature contains its own api.ts file with feature-specific queries and mutations.

Because we're using Axios as the client, we can leverage all its advanced features, including interceptors, request cancellation, and more. For additional information on Axios, you can check out their website here.

To make writing queries and mutation even easier we are using react-query-kit, a simple toolkit that makes ReactQuery reusable and type-safe with less boilerplate.

:::tip

You recommend reading Those guides and checking those resources to learn more about react-query:

:::

Data Fetching Use Cases

Suppose you're building a blog app and need to add the following features:

  • A Feed Screen that displays all posts
  • A Post Screen that shows the details of a single post
  • A Screen to create a new post

To get started, create an api.ts file within your feature folder (e.g., src/features/feed/api.ts). This file will hold all the feature's API logic including queries and mutations. You can apply this same pattern to any other features in your application.

Feed Screen

The feed screen will show all the posts available in the app. To achieve this, we need to create a hook called usePosts that will fetch the posts and display them as a list.

Here are the steps to create the usePosts hook:

  1. Inside your feature folder (e.g., src/features/feed/), create or open the api.ts file.
  2. Define the type for your Response and Variables if required, to ensure that you receive the correct data from the server. For instance, you could create a Post type for the posts.
  3. Use the createQuery function from react-query-kit library to create a query hook that will fetch the data from the server. We'll name it usePosts hook.

Below is an excerpt from the feature's api.ts file showing the usePosts query:

the createQuery function accept an object with the following: queryKey, fetcher and options. Since we migrated to the latest version of react-query-kit, the queryFn property is replaced with fetcher and the queryKey structure is simplified. Read more about (createQuery)[https://github.com/liaoliao666/react-query-kit#createQuery]

:::tip
Use useq snippet to generate a query in no time using VSCode

:::

Now that we have created our custom hook, we can use it in our screen to display a list of posts. Follow the steps below to achieve this:

  1. Create a new screen in your feature folder (e.g., src/features/feed/feed-screen.tsx).
  2. Import and use the usePosts hook we created earlier to fetch the list of posts.
  3. Use the fetched data to display a list of posts.

As you can see in the code above, we use the usePosts hook to fetch data and handle the loading state. This allows us to display a loading indicator while the data is being fetched, and then display the list of posts once the data is ready.

In the same example above we also show an error message if the request fails.

Post Screen

The post screen will display the details of a single post. To fetch and display the post, we will create a new hook called usePost.

We can use the same steps we used earlier to create the usePosts hook. The only difference is that we will use the id of the post as a variable to fetch the specific post.

Add the usePost query to your feature's api.ts file alongside the usePosts hook:

Now our hook is ready to be used in our post details screen:

Add new post

To add a new post, we can use the createMutation function from the react-query-kit library.

Here are the steps to create the useAddPost hook:

  1. Open your feature's api.ts file (e.g., src/features/feed/api.ts).
  2. Define a type for your Variables and Response to ensure that you are sending the correct data to the server.
  3. Use the createMutation function from react-query-kit library to create a mutation hook that will send the data to the server. We'll name this hook useAddPost.

Add the useAddPost mutation to your feature's api.ts file:

:::tip
Use usem snippet to generate mutation in no time using VSCode

:::

Now that we have our mutation hook ready. Let's create a new screen called AddPostScreen and use data from useAddPost hook to create a new post:

Exactly the same way we did in form section while creating a login form, we will follow the same approach to create a from to create a new post.

  1. Create the schema for the new form using Zod
  2. Create the form using TanStack Form
  3. Get mutate function from useAddPost hook and call it when the form is submitted
  4. You can use the isPending state to display a loading indicator while the data is being sent to the server and then redirect the user to the feed screen on success.

Create API Hooks with Vs Code Snippets

Maybe you mentioned that creating a new query or mutation hook looks the same every time and you are right. That is why we created a set of vscode snippets to help you create your hooks in no time.

  • useq : Create a new query hook

  • useqv : Create a new query hook with variables

  • useiq : Create a new infinite query hook

  • usem : Create a new mutation hook

React Query dev tools plugin

For managing and and monitoring the React Query instances, we use the React Query dev tools plugin, which offers us visibility into our data fetching processes and caching in real-time.
It gives the ability to refetch the data manually, inspect and remove queries, providing control over our data.
To use it, in the terminal press shift + m and choose from the opened list of dev tools the React Query plugin. The plugin's web interface will open and display the queries, enabling efficient debugging like in the example below:


Src Content Docs Guides Internationalization (`docs/src/content/docs/guides/internationalization.mdx`)


title: Internationalization
head:

  • tag: title
    content: Internationalization | React Native / Expo Starter

import CodeBlock from '../../../components/code.astro';

The starter comes with a basic internationalization setup. It uses expo-localization and i18next to provide a simple way to translate your app.

Adding a new language

Mainly the demo app supports two languages: English and Arabic (RTL). You can add more languages by adding the translation files in the src/translations folder and adding the language code to the src/lib/i18n/resources.ts file.

:::tip
Anything related to internationalization should be found in the src/lib/i18n folder.
:::

Using translations in your app

The i18n core module provides a set of utility functions to help you use translation in your app.

  • useTranslation hook from react-i18next: to get the translation function.
tsx
import React from 'react';
import { useTranslation } from 'react-i18next';

import { Text } from '@/components/ui';

export const Foo = () => {
  const { t } = useTranslation();
  return (
    <>
      <Text className="text-center">{t('settings.language')}</Text>
    </>
  );
};

or as Text component comes with translation support, you can easily use it as follows:

tsx
import React from 'react';

import { Text } from '@/components/ui';

export const Foo = () => {
  return <Text className="text-center" tx="settings.language" />;
};
  • useSetLanguage hook: to change the current language. This hook returns the selected language and a function to change the language.

Additionally, the useSetLanguage hook will save the selected language in device storage using MMKV and will be used as the default language when the app is opened again As well as adding some extra config for RTL languages while updating the selected language.

Robust translation

Typescript support

To make the translation for your app more robust and easy to maintain, we have added TypeScript support for the translation function. This ensures that you are using the correct translation key and provide auto-completion for the translation keys.
Here is an example of the error you will get if you are using the wrong translation key.

Eslint rules

We have also added ESLint rules to ensure that your translation file resources are always up to date. These rules will run as a pre-commit hook whenever you attempt to commit changes to the translation files.

We utilize eslint-plugin-i18n-json alongside a customized validation script for i18next. This script helps sort keys, ensures that all resource files are identical and prevents any missing translations.


Src Content Docs Guides Navigation (`docs/src/content/docs/guides/navigation.mdx`)


title: Expo Router
head:

  • tag: title
    content: Expo Router | React Native / Expo Starter

import CodeBlock from '../../../components/code.astro';

expo-router is a navigation library provided by Expo that simplifies the implementation of navigation in React Native applications. It is built on top of React Navigation, a widely used navigation library, and abstracts away much of the complexity involved in managing navigation state and transitions between screens.

Navigation in Expo Router is expressed declaratively, utilizing components to define the flow of the application. This approach makes it intuitive for developers to structure their navigation hierarchy.

Conventional React Native projects typically adopt a structure where a sole root component is commonly specified in either ./App.js or ./index.js. Within the context of Expo Router, an alternative approach is offered through the utilization of the Root Layout, located in app/_layout.tsx in our Demo. Thereby, the _layout section of our app handles the overall structure and navigation setup.

The Demo app comes with a simple stack and tabs layout. Feel free to remove what is not working for you and add your own using the same approach as the existing ones.

Here is a simple example of the tabs layout.

Make sure to check the official docs for more information and examples about expo-router.


Src Content Docs Guides Storage (`docs/src/content/docs/guides/storage.mdx`)


title: Storage
head:

  • tag: title
    content: Storage| React Native / Expo Starter

import CodeBlock from '../../../components/code.astro';

Storage

The starter comes with a simple storage module that uses react-native-mmkv to store data in a key-value format. We also added a simple storage utility to assist you in using the storage module.

The react-native-mmkv library provides various features such as using hooks and adding encryption to stored data. Feel free to check the official docs for more information.


Src Content Docs Guides Upgrading Deps (`docs/src/content/docs/guides/upgrading-deps.mdx`)


title: Upgrading project dependencies
head:

  • tag: title
    content: Upgrading project dependencies | React Native / Expo Starter

import CodeBlock from '../../../components/code.astro';

:::note
Please note that this guide is intended for updating your project dependencies only, and it does not ensure that your project will be in sync with the latest version of the starter.

To take advantage of the latest improvements in the starter project, you will need to compare your osMetadata.initVersion with the latest release on GitHub, and manually apply any necessary changes to your project.
:::

Easy way 🚀

Upgrading a React Native project to the latest version can be a challenging task. However, as we are using Expo with a custom dev client, the upgrade process is relatively straightforward compared to a bare React Native project, which requires a significant amount of manual work.

To update most of the dependencies that come with the starter, you can follow these simple steps:

First, go to GitHub and compare your osMetadata.initVersion in your package.json with the latest release. Check if the package.json has been updated. Then, copy the new dependencies and devDependencies versions and paste them into your package.json file.

Finally, run the following commands to make sure everything is working as expected:

bash
rm -rf node_modules ## remove node_modules folder
pnpm install ## install new dependencies
pnpm run doctor ## check for any issues with the dependencies you added to your project
pnpm lint ## linting
pnpm type-check ## type checking
pnpm test ## tests
pnpm prebuild --clean ## clean ios and android build folders and regenerate them
pnpm ios ## run ios build
pnpm android ## run android build
pnpm start -c ## start the server

:::info
If your starter version does not have the osMetadata.initVersion property, don't worry. Simply follow the steps in the rest of this guide to upgrade your project.

Moreover, if you have added new dependencies to your project (which is common for most projects), the remaining sections of this guide will also assist you in upgrading your project's dependencies."
:::

Upgrade Expo

Visit the npm package registry here to ensure that you are getting the latest version of Expo.

Once you have confirmed that you have the latest version, open your terminal and enter the following command:

bash
pnpm add expo@latest
## pnpm add [email protected] for example

Thanks to the amazing work of the expo team, you can run the following command to upgrade your dependencies to match the correct version required by Expo last version.

bash
pnpm expo install --fix

Running the command above will update your dependencies to ensure that they match the version requirements of Expo. In some cases, you may need to manually apply changes to your project in order to make it work with the new version. The command may also provide more detailed information on any necessary changes.

After updating your dependencies, run expo doctor to identify and fix any other issues that may be related to the new version of Expo and your devDependencies.

bash
pnpm run doctor ## a script inside the starter

Running this command will generate a list of issues that require manual attention. In addition, it will provide a set of npm packages that need to be updated with the exact version required by the new version of Expo.

Now it's time to make sure everything is still working as expected 😀:

bash
rm -rf node_modules ## remove node_modules folder
pnpm install ## install new dependencies
pnpm lint ## linting
pnpm type-check ## type checking
pnpm test ## tests
pnpm prebuild --clean ## clean ios and android build folders and regenerate them
pnpm ios ## run ios build
pnpm android ## run android build
pnpm start -c ## start the server

Unfortunately, there is no magic trick here in case you have any issues while running your checks, Fixing those errors may require some manual effort. You can start by reading the error message and identifying any relevant packages that may be causing the issue. Check the changelog of those packages to see if there have been any recent updates that might have introduced breaking changes. This will help you to pinpoint the root cause of the issue and take the necessary steps to resolve it.

The only rule here is to make sure that your project is working as expected before pushing your changes to the remote repository.

:::tip
Upgrading your project regularly can make the process much easier. We recommend upgrading your project a month after each new Expo major version is released. This will help you stay up to date with the latest features and security updates while minimizing the risk of encountering compatibility issues with older versions of Expo.
:::

Upgrade Non Native dependencies

While Expo upgrade will help you update most native dependencies automatically through the process outlined above, it's important to keep in mind that non-native dependencies(JS only) may also need to be updated.

This time you will use the power of pnpm to update dependencies to the last version and then run our checks to make sure everything is working as expected.

First, run the following command:

bash
pnpm up -i

When you run this command, it will generate a list of all dependencies associated with your project. You will then be prompted to select the dependencies that you want to update.

text
/* Detailed source-code truncated for AI context efficiency. */

There is no one-size-fits-all solution for managing updates to your project's dependencies. However, we recommend reviewing the changelog of each package and paying particular attention to any major version updates (indicated by red text). Major version updates may introduce breaking changes that could potentially affect your project's functionality.

We recommend to start updating devDependencies first by following this process:

  1. Select all devDependencies and update them to the latest version.
  2. Run pnpm doctor to revert any changes that are not in sync with the Expo version you are using.
  3. Run your checks to make sure everything is working as expected.
bash
rm -rf node_modules ## remove node_modules folder
pnpm install ## install new dependencies
pnpm lint ## linting
pnpm type-check ## type checking
pnpm test ## tests
pnpm prebuild --clean ## clean ios and Android build folders and regenerate them
pnpm ios ## run ios build
pnpm android ## run android build
pnpm start -c ## start the server

If you are lucky enough and everything works as expected without any issues, you can now update the rest of the dependencies.

:::tip
In case your application is not tested enough, it's a good idea to take some time to go through the application and ensure that the most critical features are working correctly before committing any changes.
:::


Src Content Docs Recipes Sentry Setup (`docs/src/content/docs/recipes/sentry-setup.mdx`)


title: Sentry Setup
head:

  • tag: title
    content: Sentry Setup | React Native / Expo Starter

import CodeBlock from '../../../components/code.astro';
import { Steps } from '@astrojs/starlight/components';

Sentry is one of the most popular solution for error reporting in the javascript ecosystem and has a great integration with Expo, we have been using it for a while and it has been working very well.

The starter kit did not come with Sentry pre-configured, but it's very easy to setup and this guide will walk you through the process.

Install and configure Sentry

1. Create a new [Sentry account](https://sentry.io/signup/) if you don't have one already. Once logged in, create a new project for your React Native app.
  1. During project creation, pay close attention to and note down the following important details:

    • Organization slug

    • Project name

    • DSN

      we will use those details next to configure the Sentry SDK in your app.

  2. Now you need also to generate a new Auth Token so you can use it to upload source maps to Sentry. To generate a new Auth Token you need to go to Developer Settings > Auth Tokens and create a new token.

    • Copy and securely store the generated token. You'll need this for configuring source map uploads.
  3. At this point, you should have the following environment variables that need to be added to your .env files:

    bash
    SENTRY_ORG=your_sentry_organization_slug
    SENTRY_PROJECT=your_sentry_project_name
    SENTRY_DSN=your_sentry_dsn

    :::note
    You can use the same Sentry configuration for all app variants (development, preview, production) as Sentry allows you to filter errors by app ID or package name in the dashboard. This simplifies setup and management while still providing the ability to distinguish between different environments.
    :::

    It's crucial to add these variables to env.ts for validation. SENTRY_ORG and SENTRY_PROJECT should be added as build-time variables, while SENTRY_DSN should be added as a client variable.

    Update your env.ts file as follows:

    ts title='env.ts'
    // ... existing imports and configurations
    
    const client = z.object({
      // ... other client env vars
      SENTRY_DSN: z.string().min(1, 'SENTRY_DSN is required'),
    });
    
    const buildTime = z.object({
      // ... other build-time env vars
      SENTRY_ORG: z.string().min(1, 'SENTRY_ORG is required'),
      SENTRY_PROJECT: z.string().min(1, 'SENTRY_PROJECT is required'),
    });
    
    const _clientEnv = {
      // ... other client env vars
      SENTRY_DSN: process.env.SENTRY_DSN,
    };
    
    const _buildTimeEnv = {
      // ... other build-time env vars
      SENTRY_ORG: process.env.SENTRY_ORG,
      SENTRY_PROJECT: process.env.SENTRY_PROJECT,
    };
    
    // ... rest of the file

    :::note
    The SENTRY_AUTH_TOKEN should not be added to the .env file as it's sensitive information that shouldn't be exposed or pushed to version control. Instead, add it as an EAS secret using the Expo dashboard or the EAS CLI:

    bash
    eas secret:create --scope your-project-name --name SENTRY_AUTH_TOKEN --value your-token-value --type string

    This ensures that your Sentry authentication token remains secure while still being accessible during the build process.
    :::

  4. Now you can install the Sentry SDK in your project.

    bash
    npx expo install @sentry/react-native
  5. Add Sentry plugin config to your app.config.ts file.

    tsx title='app.config.ts'
    // rest of the file
    
    import { ClientEnv, Env } from './env';
    
    export default ({ config }: ConfigContext): ExpoConfig => ({
      ...config,
      // rest of the config
    
      plugins: [
        // rest of the plugins
        [
          '@sentry/react-native/expo',
          {
            url: 'https://sentry.io/',
            organization: Env.SENTRY_ORG,
            project: Env.SENTRY_PROJECT,
            note: 'Ensure you set the SENTRY_AUTH_TOKEN as an environment variable to authenticate with Sentry. Do not add it to the .env file. Instead, add it as an EAS secret or as an environment variable in your CI/CD pipeline for security.',
            // If you are using a self-hosted instance, update the value of the url property
            // to point towards your self-hosted instance. For example, https://self-hosted.example.com/.
          },
        ],
      ],
    });
  6. Update your metro config to inject debug ID intro your source maps

    js title='metro.config.js'
    /* eslint-env node */
    // this replaces `const { getDefaultConfig } = require('expo/metro-config');`
    const { getSentryExpoConfig } = require('@sentry/react-native/metro');
    const { withUniwind } = require('uniwind/metro');
    
    const config = getSentryExpoConfig(__dirname);
    
    module.exports = withUniwind(config, { input: './global.css' });
  7. Now you are ready to initialize Sentry in your app.
    Create a new file src/lib/sentry.ts and add the following code:

    tsx title='src/lib/sentry.ts'
    import { Env } from '@env';
    import * as Sentry from '@sentry/react-native';
    import { useNavigationContainerRef } from 'expo-router';
    import { useEffect } from 'react';
    
    const navigationIntegration = Sentry.reactNavigationIntegration({
      enableTimeToInitialDisplay: true,
    });
    
    export const initSentry = () => {
      if (!__DEV__) {
        Sentry.init({
          dsn: Env.SENTRY_DSN,
          integrations: [navigationIntegration],
        });
      }
    };
    
    export const useSentryNavigationConfig = () => {
      const navigationRef = useNavigationContainerRef();
    
      useEffect(() => {
        if (navigationRef && !__DEV__) {
          navigationIntegration.registerNavigationContainer(navigationRef);
        }
      }, [navigationRef]);
    };

    Then, initialize Sentry and configure it with navigation in your src/app/_layout.tsx file:

    tsx title='src/app/_layout.tsx'
    import { initSentry, useSentryNavigationConfig } from '@/lib/sentry';
    import * as Sentry from '@sentry/react-native';
    
    initSentry();
    
    function RootLayout() {
      useSentryNavigationConfig();
    
      return (
        <Providers>
          <Stack>
            <Stack.Screen name="(app)" options={{ headerShown: false }} />
            <Stack.Screen name="onboarding" options={{ headerShown: false }} />
            ...
          </Stack>
        </Providers>
      );
    }
    
    // Wrap your app with Sentry
    export default Sentry.wrap(RootLayout);

    This setup will enable Sentry error tracking and performance monitoring in your app.

  8. One last thing is to add Apple privacy manifest to prevent any issues with Apple.
    Create a new file apple-privacy-manifest.json and add the following code:

    text

/* Detailed source-code truncated for AI context efficiency. */

text
Then add it to your `app.config.ts`

   ```ts title='app.config.ts'
   import applePrivacyManifest from './apple-privacy-manifest.json';
   export default ({ config }: ConfigContext): ExpoConfig => ({
     ...config,
     // rest of the config
     ios: {
       // rest of ios config
       privacyManifests: applePrivacyManifest,
     },
   });

Read more about Apple Privacy Manifest and sentry

  1. Now you are ready to test Sentry integration. Follow these steps to ensure errors are being reported correctly:

    1. Run the prebuild command for your project.
    2. Launch the app in a simulator or on a physical device.
    3. Use the following code snippet to add error-triggering buttons to your app:
    tsx
    import React from 'react';
    import { View, Button } from 'react-native';
    import * as Sentry from '@sentry/react-native';
    
    const SentryTestComponent = () => {
      const throwJSError = () => {
        throw new Error('Test JavaScript Error for Sentry');
      };
    
      const triggerNativeError = () => {
        Sentry.nativeCrash();
      };
    
      return (
        <View>
          <Button title="Trigger JS Error" onPress={throwJSError} />
          <Button title="Trigger Native Error" onPress={triggerNativeError} />
        </View>
      );
    };
    
    export default SentryTestComponent;
    1. Implement this component in your app and interact with the buttons.
    2. Check your Sentry dashboard to verify that the errors are being reported correctly (make sure to wait a little bit for the errors to appear).

    Remember to remove or disable these test buttons before releasing your app to production.

More Resources


Src Content Docs Testing End To End Testing (`docs/src/content/docs/testing/end-to-end-testing.mdx`)


title: End-to-End Testing
head:

  • tag: title
    content: End-to-End Testing | React Native / Expo Starter

import CodeBlock from '../../../components/code.astro';

Writing end-to-end tests is one of the best ways to ensure that your app is working as expected and simulate exactly how a user would interact with it. However, running those tests can be challenging and time-consuming due to their nature and the need to run them on a device or emulator.

The good news is that we have included Maestro the easiest way to run end-to-end tests yet very powerful and come up with a lot of features that will make your life easier.

One of Maestro's best features is its user-friendly test writing capability. It's incredibly simple to write tests using the app, making it accessible to PMs, QAs, and even designers.

:::tip
Watch this talk by the amazing Henry Moulton for more details about Maestro and how it can help you write better tests: Henry Moulton – React Native end-to-end testing with Maestro | App.js Conf 2023

:::

Installing Maestro

To install Maestro, run the following :

bash
# a simple npm script that runs the following command:  curl -Ls 'https://get.maestro.mobile.dev' | bash
pnpm install-maestro

Writing tests

Go to the maestro directory and write your tests.
Here is a quick test we wrote for log in screen using Maestro:

Read more about how to write end-to-end test with Maestro here

To run the tests, execute the following command:

bash
pnpm e2e-test

This will run the tests on the local emulator and here is a recording of the test from maestro dashboard:

Running tests on CI

Running tests on CI is very important to ensure that your code doesn't break when you push it to GitHub. However, running end-to-end tests is quite expensive and time-consuming. To test your app on CI, we need to first build the app, set up the emulator, and then run the tests. This process can consume a significant amount of time and resources.

For this reason, we do not recommend running end-to-end tests on every pull request pushed to GitHub. Instead, we recommend running them for pull requests that contain test-android labels so that we can trigger the tests manually by adding the correct label whenever you feel the PR is ready to be tested.

The starter comes with a bunch of GitHub actions that will help you run the tests on CI.

for now we only support running tests on Android, we are working on adding support for iOS.

  • e2e-android.yml - Triggered when a PR is labeled with android-test-github or run manually from the actions tab in GitHub.
    This action will generate a preview build for the app, set up the emulator, and run the tests using Maestro.

  • e2e-android-maestro.yml - Triggered when a PR is labeled with android-test-maestro-cloud or run manually from the actions tab in GitHub.
    This action will run the tests using Maestro Cloud. You will need to add your Maestro Cloud token to the secrets in GitHub.

  • e2e-android-eas-build.yml - Accepts a EAS APK URL and runs the tests. This action is triggered manually from the actions tab in GitHub.


Src Content Docs Testing Unit Testing (`docs/src/content/docs/testing/unit-testing.mdx`)


title: Unit Tests
head:

  • tag: title
    content: Unit Tests | React Native / Expo Starter

import CodeBlock from '../../../components/code.astro';

As we mention in the overview page of the testing section, we include the Jest testing framework and the React Native Testing Library for unit tests, along with mocks for most libraries.

The following guide is not a tutorial on how to write tests using React Native Testing Library and Jest, but rather a guide on how to write tests with this starter and some best practices to follow. If you are not familiar with testing, we recommend reading the official documentation of Jest and React Native Testing Library to get familiar with them.

Also worth mentioning that we should aim to test the following:

  • Business logic: Test component and function utilities that contain business logic. Form validation, data manipulation and calculations, etc.

  • Complex components: Test components that contain complex logic. For example, components that contain a lot of conditional rendering, or components that contain a lot of state management logic.

Writing tests

Let's start by writing a simple test for our login screen. We will test the following login form component:

:::tip

To make it easier to write tests, we are separating the form from any other logic related to API calls, navigation, etc. This way, we can test the form logic without having to worry about other things.

As you may have noticed, writing code with the intention of writing tests for it will assist us in producing cleaner code.
:::

Now, let's write a test for the login form component. We will test the following:

  • The form renders correctly.
  • Show the correct error messages on invalid or missing data.
  • Submit the form with valid data and make sure that the onSubmit function is called with the correct data.

First, let's create a new file called login-form.test.tsx in the src/features/auth/components directory. Then, add the following code to it:

As you may notice from the code, we are importing a bunch of things from the @/lib/test-utils directory. This is a simple file that exports everything from the @testing-library/react-native library and overrides the render function to wrap the component with the providers we need. This way, we don't have to import the providers in every test file.

:::tip
You can update this file to add any other providers you need to wrap your components with as well as any other utility functions you need to use in your tests.

use setup function in case you need to test interactions with the component. It returns a user (userEvent) object that you can use to interact with the component.
:::

Now that we have our test file ready, let's run it and see what happens. To run the test, run the following command:

bash
pnpm  test
pnpm test:watch # To run the tests in watch mode

Tests on CI with GitHub actions

It's important to run tests on CI in addition to local testing. This ensures that our code doesn't break when we push it to Github. We have added a GitHub action that runs tests for every push to the main branch or new pull request. It reports the results to GitHub through annotations and provides a summary of the tests along with coverage.

Here is an example of the output of the GitHub action:

:::tip
Aiming for 100% test coverage is not always a good idea and doesn't always make sense. Same with testing views and components that only render a UI
:::

More tests

For more complex logic and components, we recommend taking a look at this amazing project which provides a lot of examples and best practices for testing React Native apps using React Native Testing Library and Jest:

⚡️ React Native Testing

React Native Testing Library Cookbook


Src Content Docs Ui And Theme Forms (`docs/src/content/docs/ui-and-theme/Forms.mdx`)


title: Forms
head:

  • tag: title
    content: Forms | React Native / Expo Starter

import CodeBlock from '../../../components/code.astro';

Forms with TanStack Form

This template uses TanStack Form for form state management, providing a powerful and flexible solution with excellent TypeScript support.

Why TanStack Form?

  • Type-safe: Full TypeScript support with strong typing
  • Flexible validation: Works seamlessly with Zod schemas
  • Performance: Efficient re-rendering with granular subscriptions
  • Framework agnostic: Works with React Native
  • Simple API: Intuitive field and form management

Basic Form Example

Here's a simple login form using TanStack Form:

text
/* Detailed source-code truncated for AI context efficiency. */

Form Validation with Zod

TanStack Form integrates seamlessly with Zod for schema validation. Define your schema outside the component to prevent recreation on each render:

tsx
const schema = z.object({
  email: z
    .string()
    .min(1, 'Email is required')
    .email('Invalid email format'),
  password: z
    .string()
    .min(6, 'Must be at least 6 characters'),
});

const form = useForm({
  validators: {
    onChange: schema as any, // Real-time validation as user types
  },
  // ... other options
});

The onChange validator runs your Zod schema on every field change, providing immediate feedback to users.

Field Error Handling

The template includes a getFieldError utility function that extracts error messages from form fields:

tsx
import { getFieldError } from '@/components/ui/form-utils';

<form.Field
  name="email"
  children={(field) => (
    <Input
      error={getFieldError(field)}
      // ... other props
    />
  )}
/>

This utility:

  • Only shows errors after a field is touched
  • Handles both string errors and Zod error objects
  • Extracts the first error message for display

Form State Management

Access form state using the form.Subscribe component with selective subscriptions to optimize re-renders:

tsx
<form.Subscribe
  selector={(state) => [state.isValid, state.isSubmitting]}
  children={([isValid, isSubmitting]) => (
    <Button
      disabled={!isValid}
      loading={isSubmitting}
      onPress={form.handleSubmit}
    />
  )}
/>

Available state properties:

  • isValid - Whether the form passes validation
  • isSubmitting - Whether form is currently submitting
  • canSubmit - Whether form can be submitted (touched + valid)
  • isDirty - Whether form values have changed from defaults

Field API

Each field render prop provides access to field state and handlers:

tsx
<form.Field
  name="email"
  children={(field) => {
    // Available properties:
    field.state.value       // Current field value
    field.handleChange      // Update value handler
    field.handleBlur        // Blur event handler
    field.state.meta.isTouched  // Whether field has been interacted with
    field.state.meta.errors     // Array of validation errors

    return <Input {...props} />
  }}
/>

Working with Select Components

The same pattern applies to Select components:

tsx
<form.Field
  name="category"
  children={(field) => (
    <Select
      label="Category"
      value={field.state.value}
      onSelect={(value) => field.handleChange(value)}
      options={categories}
      error={getFieldError(field)}
    />
  )}
/>

Note: Use onSelect instead of onChangeText for Select components.

Real-World Example: Form with API Integration

See src/features/feed/add-post-screen.tsx for a complete example that demonstrates:

  • Form submission with TanStack Query mutation
  • Loading states from API calls
  • Success and error notifications with react-native-flash-message
  • Multiline text input usage
text
/* Detailed source-code truncated for AI context efficiency. */

Best Practices

  1. Define Zod schema outside component - Prevents recreation on each render and improves performance
  2. Use getFieldError utility - Consistent error extraction across all forms
  3. Subscribe to specific state - Use selector to optimize re-renders by only subscribing to needed state
  4. Validate on change - Provide immediate feedback to users with onChange validator
  5. Show errors after touch - Better UX than showing errors immediately on render
  6. Combine loading states - When using with TanStack Query, combine isSubmitting and isPending for accurate loading state

Common Patterns

Optional Fields

tsx
const schema = z.object({
  name: z.string().optional(),
  email: z.string().email(),
});

Conditional Validation

tsx
const schema = z.object({
  acceptTerms: z.boolean(),
  email: z.string().email(),
}).refine(
  (data) => data.acceptTerms === true,
  { message: 'You must accept the terms', path: ['acceptTerms'] }
);

Async Validation

tsx
const form = useForm({
  validators: {
    onChange: schema,
    onSubmitAsync: async ({ value }) => {
      // Async validation logic
      const isUnique = await checkEmailUnique(value.email);
      if (!isUnique) {
        return {
          fields: {
            email: 'Email already exists',
          },
        };
      }
    },
  },
});

Handling Keyboard

The template comes with react-native-keyboard-controller pre-installed and configured to handle the keyboard. You only need to check the documentation and use the appropriate approach for your use case. (Note that we already added the KeyboardProvider to the layout in the root file)

Make sure to check the following video for more details on how to handle keyboard in React Native:

Resources


Src Content Docs Ui And Theme Components (`docs/src/content/docs/ui-and-theme/components.mdx`)


title: UI Components
head:

  • tag: title
    content: UI Components | React Native / Expo Starter

import { FileTree } from '@astrojs/starlight/components';
import CodeBlock from '../../../components/code.astro';

The starter comes with a set of basic components and a simple design system based on Uniwind to help you get started and save you time.

All those components can be found in the src/components/ui folder. Our philosophy is to keep the components as simple as possible and to avoid adding too much logic to them. This way, they are easier to reuse and customize.

Based on your needs, you can either use them as they are or customize them to fit your needs. You can also create new ones based on the same approach.

  • ui ## core ui and theme configuration
    • button.tsx
    • checkbox.tsx
    • colors.js
    • focus-aware-status-bar.tsx
    • icons/
    • image.tsx
    • index.tsx
    • input.tsx
    • list.tsx
    • modal.tsx
    • progress-bar.tsx
    • select.tsx
    • text.tsx
    • utils.tsx

:::tip
To save time when creating new components or screens, we can simply start typing comp and press Enter to generate a new component with the correct structure.

:::

List

The List component references the FlashList component from the @shopify/flash-list package.

Props

  • All @shopify/flash-list Props are supported

We also provide an EmptyList component that you can use to display a message when the list is empty. Feel free to customize it to fit your needs.

Use Case

tsx
import * as React from 'react';
import { List, EmptyList, Text } from '@/components/ui';

const MyComponent = () => {
  return (
    <List
      data={['Item 1', 'Item 2']}
      renderItem={({ item }) => <Text>{item}</Text>}
      ListEmptyComponent={<EmptyList message="No items" />}
    />
  );
};

Image

For the Image component, we use the expo-image library to provide a fast and performant image component. The Image component is a wrapper around the Image component from expo-image package with additional styling provided by uniwind.
The cssInterop function from uniwind is used to apply styling and, in this way, the className property is applied to the style property of the Image component.

Props

  • All expo-image Props are supported
  • className - Tailwind CSS class names

Use Case

tsx
import * as React from 'react';
import { Image } from '@/components/ui';

const MyComponent = () => {
  return (
    <Image
      className="w-32 h-32"
      source={{
        uri: 'https://images.unsplash.com/photo-1524758631624-e2822e304c36',
      }}
    />
  );
};

Text

With this custom Text component, you can use the translation key as the tx prop, and it will automatically translate the text based on the current locale, as well as support right-to-left (RTL) languages based on the selected locale.

:::tip
You can also use the t snippet to create a simple Text with a default className.
:::

Props

  • All React Native Text Props are supported
  • className - Tailwind CSS class names
  • tx - Translation key

Use Case

tsx
import * as React from 'react';
import { Text, View } from 'react-native';

const MyComponent = () => {
  return (
    <View className="flex flex-col items-center justify-center">
      <Text className="text-2xl" tx="welcome" />
      <Text className="text-md" className="text-base">
        Hello world
      </Text>
    </View>
  );
};

Button

The starter comes with a simple Button component that you can use to create a basic Pressable with a Text using Tailwind CSS classes and variant definitions. These variants' logic is based on the tailwind-variants package.

The tv function from tailwind-variants is used to create a function that generates a styling configuration object for the Button component based on slot definitions, variant, size , disabled status, full-width, and default variants. Consequently, the styles defines the styles for the Button based on the provided props using the button function.

Each variant should include styles for the container, indicator, and label keys. The container style is for the Pressable, the label style is for the Text component, and the indicator style is for the ActivityIndicator component when the loading prop is true.

Props

  • All React Native Pressable Props are supported.
  • variant - Button variant, one of variant objects keys (default: default)
  • loading - Show loading indicator (default: false)
  • label - Button label
  • size - Button size, one of variants size objects keys (default: default)
  • className - Tailwind CSS class names to be applied to the Button's container
  • textClassName - Additional styling for the Button's label

Use Case

Input

We provide a simple Input component with a Text component for the label and a TextInput component for the input.

You can use it in the same way you use the TextInput component from React Native, but with additional props to customize the label and error styling.

The component utilizes the tv function from Tailwind Variants to define styling slots and variants for different states such as focused, error, and disabled. These styles are applied dynamically based on the component's state and props.

We tried to keep the Input component as simple as possible, but you can add more functionality, such as onFocus and onBlur, or adding left and right icons to the input.

Props

  • All React Native TextInput Props are supported
  • label - Input label
  • error - Input error message

For form handling with TanStack Form, use the Input component directly with form.Field. Read more about Handling Forms here.

Use Case

tsx
import * as React from 'react';
import { Input, View } from '@/components/ui';

const MyComponent = () => {
  return (
    <View className="flex flex-col items-center justify-center">
      <Input label="Email" error="Email is required" />
    </View>
  );
};

We provide a simple Modal component using the @gorhom/bottom-sheet library to display a modal at the bottom of the screen.

We opt to use a bottom sheet instead of a modal to make it more flexible and easy to use. as well as having full control over the logic and the UI.

Based on your needs, you can use the Modal if you don't have a fixed height for the modal content.

Props

  • All @gorhom/bottom-sheet Props are supported
  • children - Modal content
  • title: string - Modal title

Use Case

tsx
import * as React from 'react';
import { Modal, useModal, View, Button, Text } from '@/components/ui';

const MyComponent = () => {
  const modal = useModal();

  return (
    <View className="flex flex-col items-center justify-center">
      <Button variant="primary" label="Show Modal" onPress={modal.present} />
      <Modal ref={modal.ref} title="modal title" snapPoints={['60%']}>
        <Text>Modal Content</Text>
      </Modal>
    </View>
  );
};

Select

We provide a simple Select component using a bottom sheet with a simple List component to select an item from a list of items.

We opt to use a bottom sheet instead of a dropdown to make it more flexible and easy to use on both iOS and Android and also to minimize the number of dependencies in the starter.

The component uses the tv function from Tailwind Variants to define styling slots and variants for different states such as error, and disabled. These styles are applied dynamically based on the component's state and props.

Feel free to update the component implementation to fit your need and as you keep the same Props signature for the Select component the component will work with our form handling solution without any changes.

Props

  • label: string - Input label
  • error: string - Input error message
  • options : array of { label: string; value: string | number } - List of items to select from
  • value : string | number - Selected item value
  • onSelect: (option: Option) => void; - Callback function to handle item selection
  • placeholder: string- Placeholder text
  • disabled: boolean - Disable select input (default: false)

Use Case

tsx
import * as React from 'react';

import type { Option } from '@/components/ui';
import { SelectInput, View } from '@/components/ui';

const options: Option[] = [
  { value: 'chocolate', label: 'Chocolate' },
  { value: 'strawberry', label: 'Strawberry' },
  { value: 'vanilla', label: 'Vanilla' },
];

const MyComponent = () => {
  const [value, setValue] = React.useState<string | number | undefined>();
  return (
    <View className="flex flex-col items-center justify-center">
      <Select
        label="Select"
        error="Select is required"
        options={options}
        value={value}
        onSelect={(option) => setValue(option.value)}
      />
    </View>
  );
};

For form handling with TanStack Form, use the Select component directly with form.Field. Read more about Handling Forms here.

Checkbox, Radio & Switch

We provide a set of three simple and customizable components including a Checkbox, a Radio, and a Switch, which share the same logic under the hood.

The Checkbox, Switch, and Radio components are very similar as they share a common structure and are supposed to handle boolean values, their primary difference being the icon they display and the associated accessibility label. Each component accepts a range of props, allowing us to customize their appearance, behavior, and accessibility features.

For handling common functionality like handling press events and accessibility states we have the Root component. It wraps its children in a Pressable component and passes along props.

Animations are applied to the icons using the MotiView component from the moti library. These animations change the appearance of the icons based on their checked state.

Props

  • All React Native Pressable Props are supported excluding onPress prop
  • onChange - (checked: boolean) => void;` - Callback function to handle component's state
  • checked - boolean- Determines the state of the component (default:false)
  • label - Component's label
  • accessibilityLabel - Component's accessibility label
  • children - Child components/elements
  • className - Tailwind CSS class names
  • disabled: boolean - Disable component (default: false)

Use Case

tsx
import { Checkbox } from '@/components/ui';

const App = () => {
  const [checked, setChecked] = useState(false);

  return (
    <Checkbox
      checked={checked}
      onChange={setChecked}
      accessibilityLabel="accept terms of condition"
      label="I accept terms and conditions"
    />
  );
};

By default the component will render a label with the text you passed as label prop and clicking on the label will toggle the component as well.

For rendering a custom Checkbox, you can use the Checkbox.Root, Checkbox.Icon, and Checkbox.Label components.

tsx
import { Checkbox } from '@/components/ui';

const App = () => {
  const [checked, setChecked] = useState(false);

  return (
    <Checkbox.Root
      checked={checked}
      onChange={setChecked}
      accessibilityLabel="accept terms of condition"
    >
      <Checkbox.Icon checked={checked} />
      <Checkbox.Label text="I agree to terms and conditions" />
    </Checkbox.Root>
  );
};

Src Content Docs Ui And Theme Fonts (`docs/src/content/docs/ui-and-theme/fonts.mdx`)


title: Fonts
head:

  • tag: title
    content: Fonts | React Native / Expo Starter

Overview

This starter uses the expo-font config plugin to load fonts natively, providing optimal performance and ensuring fonts are available immediately when your app starts.

Current Setup

The template comes pre-configured with the Inter font family including 4 weights:

  • Regular (400)
  • Medium (500)
  • SemiBold (600)
  • Bold (700)

Inter is configured as the default sans-serif font in the Tailwind CSS theme, so all text uses Inter by default.

Using Fonts in Your App

With Tailwind CSS Classes

Since Inter is configured as the default font-sans, you can use standard Tailwind font classes:

tsx
import { Text } from 'react-native';

<Text className="font-sans font-normal">Regular Text (400)</Text>
<Text className="font-sans font-medium">Medium Text (500)</Text>
<Text className="font-sans font-semibold">SemiBold Text (600)</Text>
<Text className="font-sans font-bold">Bold Text (700)</Text>

With Style Props

You can also use the font directly with React Native style props:

tsx
<Text style={{ fontFamily: 'Inter', fontWeight: '400' }}>Regular Text</Text>
<Text style={{ fontFamily: 'Inter', fontWeight: '500' }}>Medium Text</Text>
<Text style={{ fontFamily: 'Inter', fontWeight: '600' }}>SemiBold Text</Text>
<Text style={{ fontFamily: 'Inter', fontWeight: '700' }}>Bold Text</Text>

Adding a New Font

To add a new Google Font to your app, follow these steps:

1. Install the Font Package

Install the desired font from the @expo-google-fonts collection:

bash
pnpm add @expo-google-fonts/roboto

2. Update `app.config.ts`

Add the font to your expo-font plugin configuration with platform-specific settings:

text
/* Detailed source-code truncated for AI context efficiency. */

3. Update Tailwind Theme (Optional)

If you want to use the font with Tailwind CSS, add it to your theme in src/global.css:

css title="src/global.css"
@theme {
  /* Font families */
  --font-family-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
  --font-family-roboto: 'Roboto', ui-sans-serif, system-ui, sans-serif;

  /* ... other theme config */
}

4. Run Prebuild

After updating the configuration, run prebuild to apply the changes:

bash
pnpm run prebuild

Then rebuild your app:

bash
pnpm run ios  # or pnpm run android

Using Local Font Files

If you prefer to use local font files instead of npm packages:

1. Add Font Files

Place your font files in the assets/fonts directory:

text
assets/fonts/
  ├── CustomFont-Regular.ttf
  └── CustomFont-Bold.ttf

2. Update `app.config.ts`

Reference the local font files in your configuration:

ts title="app.config.ts"
plugins: [
  [
    'expo-font',
    {
      ios: {
        fonts: [
          './assets/fonts/CustomFont-Regular.ttf',
          './assets/fonts/CustomFont-Bold.ttf',
        ],
      },
      android: {
        fonts: [
          {
            fontFamily: 'CustomFont',
            fontDefinitions: [
              {
                path: './assets/fonts/CustomFont-Regular.ttf',
                weight: 400,
              },
              {
                path: './assets/fonts/CustomFont-Bold.ttf',
                weight: 700,
              },
            ],
          },
        ],
      },
    },
  ],
],

3. Run Prebuild

bash
pnpm run prebuild

Important Notes

:::caution
Font changes require running expo prebuild and rebuilding your app. They won't take effect with just a hot reload or app restart.
:::

:::info

  • iOS: Font family names are automatically extracted from the font files
  • Android: You explicitly define the fontFamily name and weight mappings
  • Using this approach, you can use fontFamily: 'FontName' with fontWeight consistently across both platforms
    :::

Resources


Src Content Docs Ui And Theme Ui Theming (`docs/src/content/docs/ui-and-theme/ui-theming.mdx`)


title: UI and Theming
head:

  • tag: title
    content: UI and Theming | React Native / Expo Starter

import CodeBlock from '../../../components/code.astro';

How we manage the UI and theming of the application.

Why [Tailwind CSS](https://tailwindcss.com/) ?

For the past few years, we have tried multiple approaches to style our React Native apps: Stylesheet API, styled-components, restyle, and more.

Right now, we are confident that using Tailwind CSS with React Native is the right solution, especially after trying Uniwind.

If you are familiar with Tailwind CSS on the web you will find it very easy to use and you can even copy past your styling from a web application and should work without issues with react native too with some minor adjustments of course.

Last but not least, Tailwind CSS was a natural choice for us, considering that most of our team members come from a web background and have had the opportunity to work with Tailwind CSS before.

:::tip

If you are not familiar with Tailwind CSS, we recommend you read the Tailwind CSS documentation first.

We would also recommend playing with the Tailwind CSS playground to get a better understanding of how it works as well as practicing by building a simple web app with it before jumping into react native.

:::

About Uniwind

Uniwind is a library that allows you to use Tailwind CSS with React Native. Uniwind achieves this by pre-compiling Tailwind CSS classes into React Native stylesheets with a minimal runtime to selectively apply the styles.

Uniwind provides an efficient development experience with a utility-first approach that simplifies the styling process by using classes and applying styles directly within JSX elements.

For more details about Uniwind you can check their documentation.

Here is an example of how your component should look like:

Configuration

Uniwind is the same as Tailwind CSS, it comes with a default theme and colors that you can override by creating your own theme and colors.

You need to understand that Uniwind is a library that is built on top of Tailwind CSS. Feel free to add any Tailwind CSS config that you want to use in your application such as updating colors, spacing, typography, etc.

We have created a ui/theme folder where you can find our custom colors that have been imported into tailwind.config.js and used as a theme for our demo application. You can add your own color palette and use them in your components with Tailwind class names.

You can read more about how to configure your project with Tailwind CSS.

Dark Mode