Home » A Practical React Native Expo Tutorial to Build Your First App
Latest Article

A Practical React Native Expo Tutorial to Build Your First App

If you're looking to build a mobile app for both iOS and Android without having to learn two different tech stacks, you've come to the right place. This guide walks you through the entire process using React Native and Expo, a powerful combination that lets you write your app once in JavaScript and deploy it everywhere.

The best part? For most of your journey, you won't even need to touch native development tools like Xcode or Android Studio. We’ll go from a blank slate all the way to a finished app on the App Stores.

Why Choose React Native and Expo

Jumping into mobile development can feel like a steep climb. You've got different languages, separate tools, and unique ecosystems for iOS and Android. React Native, supercharged with Expo, cuts through that complexity. It’s a proven, efficient approach used by everyone from solo developers to large corporations to save a massive amount of time and money.

By 2025, React Native and Expo hit all-time highs in both downloads and industry adoption. The reason for this surge is clear: Expo handles the messy, complex parts of project setup and native configuration for you. This frees you up to focus on what actually matters—building great features for your users—instead of getting bogged down in platform-specific troubleshooting.

For any business, this directly translates to a faster launch, reduced development costs, and a more streamlined project timeline. You can find more details on this trend over at NativeWeekly.

Managed Vs. Bare Workflow

One of the first, and most important, decisions you'll make is choosing between Expo’s Managed and Bare workflows. This choice dictates how much control you have over the native side of your project.

A flowchart illustrates the React Native workflow decision process, choosing between bare or managed workflows.

To make it even clearer, here’s a quick comparison to help you decide which workflow is right for your project's needs.

Expo Managed Workflow Vs Bare Workflow

FeatureManaged WorkflowBare Workflow
SetupSimple. One command to start.More complex. Requires native toolchains.
Native CodeNo direct access. Abstracted away.Full access to ios and android folders.
Custom ModulesLimited to Expo SDK/plugins.Can add any custom native module.
Build ProcessCloud-based via EAS Build.Local builds with Xcode/Android Studio.
Best ForMost apps, beginners, rapid prototyping.Apps with specific native requirements.

The key takeaway is simple: unless you know you need a specific custom native module that isn't available through Expo's vast library, start with the Managed Workflow. It’s faster and much simpler.

From my experience, I always advise developers to start with the Managed Workflow. You can concentrate entirely on building your app's features with React. If you eventually hit a limitation, you can always "eject" to the Bare Workflow later on.

Setting Up Your Environment

Getting your machine ready is surprisingly straightforward. You only need two things to get started: Node.js and the Expo CLI.

Node.js acts as the JavaScript runtime, and the Expo CLI is the command-line tool you’ll use for pretty much everything—creating projects, running the development server, and building your app.

First, make sure you have a recent version of Node.js installed. Then, open your terminal and run this one command to install the Expo CLI globally:

npm install -g expo-cli

That’s it! With those tools in place, you’re all set to create your first cross-platform app. If you want a more detailed breakdown of these initial steps, our React Native tutorial for beginners is a great resource. By the end of this setup, you’ll have a project up and running locally, ready for us to start building on.

Building A Real-World App With Expo Router

Laptop, coffee, smartphone, notebooks, and pens on a wooden desk, with 'GET STARTED' text.

Alright, theory is great, but nothing beats getting your hands dirty and building something real. This is where the concepts really start to click. We're going to build a multi-screen "Post Viewer" app using Expo Router, the file-based routing system that has quickly become the go-to for new Expo projects. If you've ever done web development with a framework like Next.js, this will feel incredibly familiar—create a file, and you've got a new route.

Our little app will be simple but practical. It will fetch a list of posts from a public API, display them in a list, and allow users to tap on a post to see its full details. This workflow is a classic for a reason; it touches on the core skills you'll use every day: UI creation, state management, and data handling.

Structuring Your Project For Growth

Before you write a single component, let's talk about structure. A messy project folder is a ticking time bomb that will make future updates a total headache. I’ve found that organizing code by feature or domain is a lifesaver on larger projects.

For our Post Viewer, we’ll keep it simple but scalable. In your project’s root directory, go ahead and create a components folder. This is where we'll stash all of our reusable UI pieces—think custom buttons, loading indicators, or styled cards.

This one small step of separating your generic components from your screens is a fundamental principle of building maintainable apps. It keeps your app directory (where Expo Router does its magic) clean and focused on just two things: navigation and screen layout.

Creating The Screens With Expo Router

The beauty of Expo Router is how it turns your file system into your navigation map. It just works. Inside the app directory that Expo created for you, we’ll define our screens.

  • Home Screen (app/index.tsx): This file should already be there. It’s the entry point of our app and will show the list of all posts.
  • Post Detail Screen (app/posts/[id].tsx): This is a dynamic route. The [id] in the filename is special syntax that tells Expo Router this screen's content depends on an ID passed in the URL (e.g., /posts/1).
  • Layout File (app/_layout.tsx): This is a special file for defining a shared UI wrapper, like a navigation header, for all screens in the same directory.

Just by creating these files, Expo Router already knows how your app is supposed to navigate. No more wrestling with giant, complex navigation configuration objects. The file system is now your source of truth.

Let's flesh out the layout. In app/_layout.tsx, we can use Expo Router's Stack component to give our app a consistent header.

// app/_layout.tsx
import { Stack } from "expo-router";

export default function RootLayout() {
return (

<Stack.Screen name="index" options={{ title: "All Posts" }} />
<Stack.Screen name="posts/[id]" options={{ title: "Post Details" }} />

);
}

This tiny bit of code sets up a standard stack navigator and customizes the header titles for our two screens. Simple and declarative.

Fetching Data And Managing State

Back in our home screen (app/index.tsx), it's time to pull in some data. We'll use the browser-standard fetch API along with React's useState and useEffect hooks to grab the posts and handle the loading state.

Here’s what a practical implementation looks like for app/index.tsx:

// app/index.tsx
import { useEffect, useState } from 'react';
import { View, Text, FlatList, ActivityIndicator, Pressable } from 'react-native';
import { Link } from 'expo-router';

// A simple type for our post data
type Post = {
id: number;
title: string;
};

export default function HomeScreen() {
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(data => {
setPosts(data);
setLoading(false);
});
}, []);

if (loading) {
return <ActivityIndicator size="large" style={{ flex: 1 }} />;
}

return (
<FlatList
data={posts}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<Link href={/posts/${item.id}} asChild>
<Pressable style={{ padding: 16, borderBottomWidth: 1, borderBottomColor: '#ccc' }}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>{item.title}


)}
/>
);
}

In this file, the useEffect hook triggers the data fetch as soon as the screen loads. We're using FlatList, which is React Native's workhorse for efficiently rendering long lists. Each item in the list is wrapped with Expo Router's Link component, which makes navigating to the detail screen as easy as providing an href.

Building The Detail Screen

Now for the posts/[id].tsx file. Its job is to figure out which post to show by grabbing the ID from the route, then fetching that specific post's data. For this, Expo Router gives us the useLocalSearchParams hook.

// app/posts/[id].tsx
import { useEffect, useState } from 'react';
import { View, Text, ActivityIndicator, StyleSheet } from 'react-native';
import { useLocalSearchParams } from 'expo-router';

type PostDetails = {
id: number;
title: string;
body: string;
};

export default function PostDetailScreen() {
const { id } = useLocalSearchParams();
const [post, setPost] = useState<PostDetails | null>(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
if (id) {
fetch(https://jsonplaceholder.typicode.com/posts/${id})
.then(response => response.json())
.then(data => {
setPost(data);
setLoading(false);
});
}
}, [id]);

if (loading) {
return <ActivityIndicator size="large" style={{ flex: 1 }} />;
}

return (

{post?.title}
{post?.body}

);
}

const styles = StyleSheet.create({
container: { padding: 16, backgroundColor: '#fff', flex: 1 },
title: { fontSize: 24, fontWeight: 'bold', marginBottom: 16 },
body: { fontSize: 16, lineHeight: 24 },
});

Personal Tip: Pay close attention to the dependency array [id] in that useEffect hook. This is absolutely critical. It tells React to re-run the fetch logic anytime the id from the URL changes. This handles cases where a user might navigate from one post directly to another without going back to the list first.

And just like that, you have a working, multi-screen React Native app. This file-based approach to routing, which is a huge part of this React Native Expo tutorial, dramatically simplifies your navigation code and helps you keep your project organized, even as it gets more complex.

Debugging And Improving App Performance

A computer monitor shows React Native development, alongside two smartphones displaying a multi-screen app.

Getting your app to work is a huge first step, but making it feel fast and polished is what separates a good app from a great one. This next phase is all about hunting down bugs and squeezing every last drop of performance out of your React Native Expo app.

After all, a slow or buggy app is one of the quickest ways to lose a user. That’s why mastering debugging and optimization is a non-negotiable skill for any serious mobile developer.

Your Go-To Debugging Toolkit

When something inevitably breaks, you need the right tools to peek under the hood and see what’s going on. The Expo ecosystem gives you some powerful options right out of the box, each built for a specific job.

Your first stop is usually the Expo Dev Tools, a web-based interface that pops up automatically when you start your development server. Think of it as your mission control. From here, you can open your app on different devices, reload it, and, most importantly, access the JavaScript debugger.

The real power lies in the JavaScript debugger. To get there, just shake your device or press d in the terminal and select "Open JS Debugger." This will open a Chrome DevTools window that's directly connected to your app. This lets you:

  • See console.log statements: This is your bread and butter for tracing how your app is running and what your variables look like.
  • Set breakpoints: You can literally pause your code on a specific line to inspect everything—the call stack, variable values—at that exact moment.
  • Analyze network requests: Check the status, headers, and payloads of any API calls your app is making. It’s invaluable for troubleshooting server issues.

For more complex problems, you might want to level up to tools like Flipper or the standalone React Native Debugger. These offer deeper insights, like native module inspection and advanced state management views for Redux. We dive deeper into these in our guide to React Native debugging essentials.

Activating The Hermes Engine For A Major Speed Boost

One of the most impactful performance upgrades you can make is switching on Hermes. It’s a JavaScript engine built by Facebook specifically for React Native. Its entire purpose is to improve startup time, use less memory, and shrink your final app size.

The great news is that for new Expo projects, Hermes is now enabled by default. You can double-check that it's active in your app.json or app.config.js file. Look for "jsEngine": "hermes" under the ios and android sections.

If for some reason it isn't enabled in an older project, adding it is simple:

{
"expo": {
"ios": {
"jsEngine": "hermes"
},
"android": {
"jsEngine": "hermes"
}
}
}

The performance gains from Hermes aren't just a nice-to-have. For many apps, it can slash the Time to Interactive (TTI) by up to 30%. That's a massive improvement in user experience, especially on lower-end Android devices where startup lag is most noticeable.

This shift toward highly optimized engines is a huge part of the framework's growing maturity. For example, Discord famously achieved 98% code sharing between its iOS and Android apps, proving that React Native can handle incredibly complex, real-time applications at scale. You can dig into more trends and see React Native's market position on Data Landbase.

Profiling And Hunting Down Bottlenecks

Sometimes, performance issues are sneaky. An app might feel sluggish only during certain animations or when scrolling a long list. This is where profiling comes in. The Performance Profiler in Chrome DevTools is your secret weapon for these situations.

To use it, open the JavaScript debugger and head to the "Performance" tab. You can hit record, perform the slow action in your app (like scrolling that laggy list), and then stop the recording. The profiler will generate a detailed "flame graph" of what just happened.

  • Wide bars in the graph are red flags—they show functions that are taking a long time to run. These are your prime suspects.
  • Look for excessive re-renders. The profiler is brilliant at helping you spot components that are re-rendering when they don't need to, a classic performance killer.

By digging into this data, you can pinpoint the exact function or component causing the slowdown. Maybe it's an expensive calculation running on every render or a FlatList that hasn't been properly optimized with memo. Fixing these kinds of issues is what turns a clunky experience into a buttery-smooth one.

Mastering Expo Application Services (EAS)

So, you've got your app running smoothly on your local machine. That's a huge milestone! But the real question is, how do you get it out of your development environment and into the hands of actual users? This is where Expo Application Services (EAS) steps in and becomes your most valuable partner.

Think of EAS as the command center for shipping your app. It's a suite of cloud-based services designed to handle the notoriously tricky parts of building, deploying, and updating a production-ready application. EAS is what bridges the gap between writing code on your laptop and reaching a global audience on the App Store and Play Store.

The Power Of EAS Build

At the core of the EAS suite is EAS Build. This service is the modern, powerful replacement for the classic expo build command, and it's built from the ground up for serious, production-grade apps. Instead of trying to run a complex and often-fragile build process on your own computer, EAS Build moves everything to a clean, optimized cloud environment.

What this means for you is no more headaches. You don't need a Mac to build your iOS app, and you don't have to fight with Android Studio configurations. EAS provides all the necessary infrastructure to compile your JavaScript into a native .ipa file for iOS and an .apk or .aab for Android.

Getting started is straightforward. First, you'll need to install the EAS CLI and log in to your Expo account right from your terminal:

npm install -g eas-cli

eas login

With the CLI ready, you just need to configure your project for EAS by running:

eas build:configure

This command is a big deal. It creates a critical file named eas.json in your project's root directory. This is where you'll define different "build profiles," which let you create specific versions of your app for different needs—like a development build for easy testing, a preview build to share with your team, and a production build for the app stores.

EAS Build is a game-changer because it standardizes the build environment. This completely eliminates the "it works on my machine" problem, ensuring every build is consistent and repeatable. That kind of reliability is absolutely critical for a professional deployment pipeline.

Classic Expo Build Vs EAS Build

To really appreciate why EAS Build is the new standard, it helps to see how it stacks up against the old expo build method. The classic approach served its purpose for simple projects, but EAS is in a different league.

Here’s a quick breakdown of what changed and why it matters:

AspectClassic expo buildEAS Build
WorkflowManaged Workflow onlySupports both Managed and Bare
Custom CodeNo custom native code allowedFully supports custom native modules
Build LocationExpo's shared cloud serversDedicated, containerized cloud builders
ConfigurationLimited, via app.jsonHighly configurable via eas.json profiles
Use CaseSimple apps, quick prototypesProduction apps, complex projects

The table makes it pretty clear: for any app you plan to launch publicly and maintain over time, EAS Build provides the power and flexibility you’re going to need. It’s the professional choice.

Instant Updates With EAS Update

One of the most powerful features in the entire Expo ecosystem is the ability to push updates directly to your users' devices, completely bypassing the slow app store review process. This magic is handled by EAS Update.

Imagine you’ve launched your app and a user reports a small but annoying bug. In the old days, you'd have to fix the code, rebuild the entire native binary, submit it to Apple and Google, and then wait days (or even weeks) for approval. With EAS Update, you can push a fix for your JavaScript code and have it live in minutes.

The service works by delivering a new bundle of your JS code and assets to your app when it launches. Your app downloads the update in the background and applies it the next time the user opens it. It's perfect for:

  • Hotfixes: Instantly squash bugs and patch issues.
  • Feature Flags: Remotely turn features on or off for users.
  • A/B Testing: Deploy different versions of your UI to different user segments to see what performs best.

You configure updates in your app.json or app.config.js, and then publish a new one with a simple command:

eas update --branch production --message "Fixing login button bug"

This command publishes the update to the "production" channel. Any build of your app configured to listen to that channel will automatically get the new code. It's an incredibly powerful tool for iterating quickly.

Automating Your Workflow With CI/CD

For a truly professional workflow, automation is key. This is where Continuous Integration and Continuous Deployment (CI/CD) come into play. EAS was designed to integrate smoothly with CI/CD providers like GitHub Actions.

You can set up a workflow that automatically triggers an EAS Build every time you merge code into your main branch. This means a new, installable version of your app gets built without you having to lift a finger. For a deeper dive into the full release process, check out our guide on how to publish a React Native application using Expo.

A common setup involves creating a simple workflow file (e.g., .github/workflows/build.yml) that runs the eas build command. This ensures that a tested, buildable version of your app is always ready for submission. Adopting this level of automation dramatically reduces human error and speeds up your entire development cycle, making it an essential practice as you scale your operations.

Submitting Your App to the App Stores

A laptop displays a complex system diagram, with a smartphone showing a mobile app interface.

This is it—the final hurdle. You’ve put in the hours to build, debug, and fine-tune your app. Now it's time to get it into the hands of actual users. This last step means navigating the submission process for both Apple’s App Store and the Google Play Store, a task that once felt like a monumental effort reserved only for native developers.

Thankfully, EAS Submit has completely changed the game. This tool, part of the Expo Application Services suite, handles most of the tedious work for you. It automates uploading your builds and even helps manage your store listings, all from the command line.

Preparing For Your First Submission

Before you type that first submission command, there's some essential prep work to do. Both Apple and Google have strict requirements for assets and store listing information. Trust me, getting all your ducks in a row now will save you from a world of frustration later. A bit of planning can turn a potential week-long headache into a smooth, one-day process.

First things first, you’ll need active developer accounts for both platforms. This is non-negotiable.

With your accounts ready, you can focus on the metadata—all the text and images that will represent your app on its store page. This is your app's first impression, so don't treat it as an afterthought.

The Essential Submission Checklist

Your store listing is your most important marketing asset. A polished, well-thought-out page can dramatically boost downloads, while a sloppy one will send potential users running. Nail these elements before you even think about submitting.

  • App Icons: You'll need a whole suite of icon sizes to look sharp on different devices and store pages.
  • Screenshots: Take high-quality images that show off your app's best features. I always recommend creating a visual story that walks a user through a key workflow.
  • App Description: Be crystal clear about what your app does, who it’s for, and the value it provides.
  • Privacy Policy: You absolutely need a public URL pointing to your app's privacy policy.
  • Keywords/Tags: These are crucial for helping users find your app when they search the store.

My Personal Tip: Always create your screenshots on the largest device screen size you can. Both stores handle scaling images down far better than scaling them up. A blurry, pixelated screenshot screams "low-quality app" to potential users.

Once your assets are polished and ready, you'll need to configure your eas.json file. This tells EAS exactly how to handle the submission. A common approach is to create a production submission profile that points to the build artifacts you've already created.

Using EAS Submit For a Smooth Launch

With all your prep work done, submitting the app is surprisingly simple. For an iOS submission, you'll just run one command in your terminal:

eas submit -p ios --profile production

EAS will then prompt you for your Apple ID and any other required credentials. From there, it takes the build you made with EAS Build and uploads it straight to App Store Connect. You'll just need to log in to the portal to complete the final submission steps. The process for Android is almost identical—just swap -p ios for -p android.

The skills you've developed throughout this React Native Expo tutorial put you in a great position. React Native currently powers about 35% of the cross-platform market, a clear sign of its maturity and the massive talent pool behind it. With giants like Shopify, Microsoft, and Meta investing heavily in the framework for their own major apps, the ecosystem is only getting stronger. You can discover more insights about the future of cross-platform development and why React Native continues to lead the pack.

Navigating App Store Rejections

Getting a rejection from a reviewer is practically a rite of passage for app developers. Don't get discouraged! It's not a failure; it's feedback.

When it happens, read the notes from the review team carefully. They will almost always point to a specific guideline you've overlooked. The most common culprits are:

  • Incomplete Information: Forgetting to provide demo account credentials or not clearly explaining your app's purpose.
  • Bugs or Crashes: Your app must be stable and run without crashing on the reviewer's device.
  • Guideline Violations: These often relate to user privacy, in-app payments, or misleading metadata.

Address the issues precisely, resubmit your build, and be sure to write clear notes for the reviewer explaining what you fixed. With a little persistence and attention to detail, you'll get your app across that finish line and onto the App Store and Play Store.

Answering Common Expo Development Questions

As you dive into this React Native Expo tutorial, you're bound to run into a few common questions. Getting these sorted out early will save you a ton of headaches and help you build your app with confidence.

Let's clear up some of the most frequent points of confusion I see from developers.

When Should I Use the Bare Workflow Instead of Managed?

My advice here is simple: stick with the Managed Workflow for as long as you can. Its real power lies in how much it simplifies development, builds, and updates. It’s a massive time-saver.

You should only consider moving to the Bare Workflow when you hit a wall—specifically, when you absolutely need a custom native module that isn’t available through the Expo SDK or a config plugin. Always start your projects in Managed. If you find yourself needing to write custom Swift/Kotlin or integrate a very specific native library, only then should you "eject" to Bare. This approach keeps your project lean and focused until native complexity is truly unavoidable.

What's the Difference Between Expo Go and a Development Build?

This is a big one. Think of Expo Go as a generic, pre-built sandbox. It’s an app you download from the store that can instantly run almost any managed Expo project. The catch? It only contains the standard, built-in Expo SDK modules.

A development build, however, is a version of your own app that you build for development purposes.

The crucial distinction is that a development build includes any custom native code you've added to your project, usually via config plugins for third-party libraries. If you install a library like a special video player or a Bluetooth package that has native components, you must use a development build to test it. Those features simply won't work in the standard Expo Go app.

Can I Really Build an App Without Ever Opening Xcode or Android Studio?

Yes, for a huge number of apps, you absolutely can. This is one of the most compelling reasons to use Expo today.

By staying in the Managed Workflow and using EAS Build, you can develop on Windows, macOS, or Linux, build your app for both iOS and Android, and submit it to the stores without ever touching a native IDE.

EAS Build takes care of all the heavy lifting and native compilation in the cloud. The only time you'd need to fire up Xcode or Android Studio is if you've ejected to the Bare Workflow to work on custom native code that a config plugin can't manage for you. For most developers, this means you can live almost entirely in your JavaScript/TypeScript code.


At React Native Coders, we publish deep-dive tutorials and news to help you master the entire mobile development lifecycle. To build, test, and ship your apps faster, explore our guides.

About the author

Prathamesh

Add Comment

Click here to post a comment