In this video, you'll bridge the gap between web and mobile with an intensive crash course on React Native fundamentals and modern features like file-based routing and server components. Then, you’ll build Recurrly, a subscription tracker using Expo. You’ll implement essential real-world features before taking the leap that most tutorials skip: publishing your app to the Apple App Store and Google Play Store so you can actually start monetizing your work.
Expo: https://jsm.dev/nativesub-expo
Expo EAS : https://jsm.dev/nativesub-eas
Clerk: https://jsm.dev/nativesub-clerk
PostHog: https://jsm.dev/nativesub-posthog
CodeRabbit: https://jsm.dev/nativesub-coderabbit
Junie AI: https://jsm.dev/nativesub-junie
WebStorm: https://jsm.dev/nativesub-webstorm
Explore my Pro Content:
⭐ Join JS Mastery Pro: https://jsm.dev/nativesub-jsm
💎 Become Top 1% Next.js Developer: https://jsm.dev/nativesub-nextjs
👨🔬 Master Next.js Testing: https://jsm.dev/nativesub-testing
📗 GSAP Animations Course: https://jsm.dev/nativesub-gsap
📕 Three.js 3D Course: https://jsm.dev/nativesub-threejs
📙 JavaScript Course: https://jsm.dev/nativesub-javascript
🚀 Launch Your SaaS: https://jsm.dev/nativesub-saas
📁 FREE Video Kit (Code, Figma, Assets, and more): https://jsm.dev/nativesub-kit
More courses launching soon! Join the waitlists to get notified! 🔥
👉 Backend Course Waitlist: https://jsm.dev/nativesub-backend
👉 Agentic Coding Course Waitlist: https://jsm.dev/nativesub-agentic
👉 React Native Course Waitlist: https://jsm.dev/nativesub-native
👉 React Course Waitlist: https://jsm.dev/nativesub-react
👉 Tailwind Course Waitlist: https://jsm.dev/nativesub-tailwind
➤ Links not working?
If you're in Nigeria, you might have to use a VPN to access the links.
➤ If something mentioned in the video isn’t listed here or a link is broken:
Leave a comment, or contact support@jsmastery.pro
Rate us on TrustPilot: https://jsm.dev/trustpilot
https://discord.com/invite/n6EdbFJ https://twitter.com/jsmasterypro https://instagram.com/javascriptmastery https://linkedin.com/company/javascriptmastery
Business Inquiries: contact@jsmastery.pro
Timestamps:
00:00:00 — Introduction
00:03:19 — Crash Course
00:13:14 — Fundamentals
00:23:44 — Project Setup
00:35:19 — Setup Styling (Nativewind)
00:45:17 — Routing & Navigation
01:03:10 — Tab Navigation
01:27:55 — Custom Fonts
01:36:11 — Home UI
02:24:13 — Clerk Setup
02:28:37 — Authentication
02:53:40 — PostHog
03:05:44 — Subscription Tab
03:10:16 — Create Subscriptions
03:13:57 — PostHog Custom Events
03:26:04 — EAS Build & Deploy
03:39:39 — Closing & What's Next
Did you know that if you already know React, you're about 75% of the way to being a mobile app developer? Most of what you've learned building websites carries over directly to building mobile applications. The components, the hooks, the state management, it all transfers. But here's what changed. React Native now isn't the React Native from 2 years ago. It's closer to Tanstack Start or Nex. js now. file-based routing, API routes, and server components. The gap between web and mobile has basically closed. Hi, I'm Adrian, and in this course, you're going to build, publish, and monetize a full stack mobile app from scratch all the way to the app store. The app is called Recurly, a subscription management app that helps you track every recurring charge in your life. You'll connect it to a real backend with Node. js, Express, and MongoDB. You'll set up authentication, implement active and inactive subscription tracking, scheduled email reminders so your users never miss a billing date, and a custom tab navigation that feels truly native. But then you're going to do what 99% of tutorials skip. You'll take this app through EAS and publish it to both the Apple App Store and Google Play Store. while at the same time learning how to charge real money for it. Here's the stack. Expo, the official framework recommended by the React Native team. Native wind to style your React Native apps using Tailwind CSS. Clerk for O and billing. Code rabbit for code reviews. And Posthog for analytics. We start with a crash course covering React Native fundamentals and Expo's latest features. Then we build and then we ship. And to make your journey even easier, I put together a free React Native road map that covers the essentials you need to know and the exact path to getting hired as a mobile developer today. You can get it from the video kit link down in the description. By the end, you won't just know React Native. And you won't just have built a mobile app, but you'll have published one to the app store that people can actually download and pay for. Look, if you're serious about leveling up from watching tutorials to actually becoming higher ready, you'll love JSMy Pro. It's where I go beyond the build this app stuff and teach the engineering mindset behind the code. Inside Pro, you get full premium courses like the ultimate Nex. js, testing, animations, JavaScript, SQL, 3JS, and more. Quizzes after every lesson, so you actually lock in what you learn. interview practice with our AI interviewer, so you can train for real technical interviews the same way you train for a sport. And we're also launching the ultimate back-end course and our AI engineering courses next. And our pro members get early access. You also get access to our private Discord where you can ask questions to real human beings and get help fast. If you want to check it out, I might give you a special discount just because you're coming from this video. Give it a shot. The link is in the description.
Crash Course
Before React Native, building mobile apps meant maintaining two completely separate code bases. One for iOS in Swift and one for Android in Cotlin. That's double the work, double the bugs, and double the cost. For most teams and especially individual developers, this just wasn't realistic. and React Native change that one codebase running across both platforms. You write plain old JavaScript and React and it renders actual native components on each platform, not just a web view or a hybrid wrapper, real native UI, which on iOS devices means Apple's latest liquid glass. Unpopular opinion, I think it actually looks pretty good. And React Native is huge. Companies like Meta, Microsoft, Discord, Tesla, Amazon, Coinbase, Shopify, and even Call of Duty all use React Native in production, but the old criticism was always performance. React Native apps feel sluggish. Well, that was a fair point 3 years ago, but not anymore. The new architecture, which shipped as the new default, replaced the old bridge system entirely. JavaScript now talks directly to native code through something called JSI which means that modules load on demand instead of loading at startup and the rendering layer syncs with React's own update cycle. In short, React Native apps are now as fast as fully native ones. So, that debate is over. Oh, and on top of that, you also get hot reloading for instant feedback while developing a massive ecosystem of community libraries. And if you already know React, you can pick this up in a day. That's what I'll prove to you. As you know, React Native is a runtime, but we'll build with Expo. If you've used Nex. js with React or Next with Vue or Tanstack Start, you already understand the concept. Expo is the framework layer on top of React Native. It handles your dev environment, your routing, your builds, and your deployments. React Native gives you the engine, and Expo gives you the car. And before anyone in the comments says, "Real developers don't use Expo. " Well, check React Native's own documentation, Expo is the officially recommended way to build React Native apps. That's not my opinion. That's the React Native Steam's recommendation. So, why does this matter to you? Well, there's a few reasons. First, no Android Studio or Xcode required to get started. You just run one command and you have a working app on your phone in under 2 minutes. Still, within this course, I'll teach you how to set up Android Studio and Xcode in order to be able to publish your app to real app stores so other users can download them and even pay for the features within them. Second, Expo handles native dependencies for you. Camera, apps, notifications, biometrics, all pre-built and tested across both platforms. Third, file-based routing. If you've used NexJS, you already know how this works. You create a file and it becomes a screen. Nested folders become nested routes and dynamic segments start with square brackets. It's the same mental model. And fourth, Expo will give you the easiest path from making the app run on your phone all the way to making the app live on the App Store. You don't need to switch tools halfway. That path is called EAS, Expo Application Services. Think of it this way. Expo helps you build your app and EAS helps you ship it. EAS build compiles your app in the cloud. No MAC needed for iOS builds. EAS submit sends it to the App Store or Google Play with one command and EAS update lets you push fixes directly to users devices without going through store review. This is huge. It's like refreshing a website but for your mobile app. There's also EAS workflows to automatically build, sign, and submit your app directly from a GitHub repo. And then there's Expo Observe to measure real user metrics like launch time and app responsiveness. With this, you can fully automate your release process and track exactly how your app performs for real users in production. And companies like Discord, Coinbase, and Xbox use EAS to ship their production apps. Of course, it's one thing to just mention all of these features, but throughout this course, you'll get hands-on with all of this a bit later when we actually deploy Recurly, our subscription management app that you'll build. So, for now, just know that the tooling exists and it's solid. So, that's your development and deployment stack. But in this course, I want to take you beyond just it works in my machine. You're going to be setting up the same tools that real companies use to authenticate users, track behavior, and catch bugs before your users do. And here's what makes this even better. Every single tool we'll use in this course is completely free to get started with. No credit card required. You can build, ship, and monitor a real app without spending a dollar. So, let me walk you through what we're using and why. First up, Clerk for authentication and billing. If you watched any of my previous courses, you already know Clerk. It handles signup, signin, user management, and session handling with just a couple of lines of code. What used to take me days of wiring up odd providers, managing tokens, and building login screens now literally takes minutes. For recurly, we'll use Clerk's custom oflow with Expo. You'll set up secure authentication, manage user sessions, and handle the entire sign-in experience. Oh, and Clerk just launched native components for Expo, which is essentially pre-built UI that renders using Swift UI on iOS and Jetack Compose on Android. We won't use them directly in this build since they require dev builds to keep the setup as simple as possible and to teach you as much as possible in the shortest amount of time, but they're totally worth exploring once you're comfortable with the fundamentals. And on top of O, we'll also use Clerk for billing. You learn how to set up subscription plans, manage payments, and enforce plan limits. This is super cool because it'll actually allow you to make money off of the apps that you build and publish to the App Store. So go ahead and create a free clerk account now. The link is in the description and you'll need this account when we get to the o section of the build. Now post hog for analytics and exploration. Building this app is only half the job because once it's live, you need to know what your users are actually doing. Which screens do they visit? Where do they drop off? And are the push notification reminders actually bringing people back? Postthog answers all of that. We'll integrate their React Native SDK and use it as our single tool for understanding user behavior. You'll set up event tracking to see which subscriptions users add most, funnel analysis to measure how many users complete on boarding, feature flags to roll out new features to only a percentage of users before going live, and air tracking to catch crashes the moment they happen. Post Hog also gives you session replay on mobile, so you can literally watch how a user navigates your app and see exactly where they get stuck. And if you want direct feedback, you can trigger in-app surveys after key moments, like asking users what subscription service they wish you supported. Post Hog's free tier is generous, more than enough for what we're building. So, create your account from the link in the description so it's ready when we get to the analytics section. And we'll also use Code Rabbit for AI powered code reviews. When you push code to GitHub, Code Rabbit automatically reviews every pull request. It reads the diff, understands the context of your entire codebase, and leaves line by line feedback, security issues, edge cases, and things that are easy to miss when you're building fast. But it doesn't just tell you what's wrong. It also suggests fixes you can apply with one click, right from the PR. Think of it as having a senior engineer reviewing every commit, except it's instant. And another huge part I love about Code Rabbit, but they don't even advertise it as a feature, is that you can learn so much from it. For our project, you'll connect Code Rabbit to your GitHub repo and see it in action as we build recurly. And as you're building this React Native application, it'll actually provide you with advice on how you can improve your codebase, write more quality code, and just understand React Native and features your implementing much more deeply on a fundamental level. It's a two-click setup, free for open- source, and has a free tier for private repos. Same as others, create your account right now with a link in the description. So, here's the full picture. Expo and EAS handle your development and deployment. Clerk handles off and billing. Posthog handles analytics and experimentation. And Code Rabbit handles code quality. That's a production stack. The same categories of tools that the biggest companies use to ship their mobile apps. And every single one of them is free for you to start with today. But before we plug any of these in, you need to understand React Native itself. how the components work, how styling works, how routing works, because that's the foundation everything else builds on. So, let's dive into the crash course.
Fundamentals
All right, let's jump right in. We've already talked about the benefits and drawbacks of React Native and how Expo can make your life easier. Now, it's time to dive into the code and see how it works. If you've worked with ReactJS before, you'll find that React Native uses a similar syntax. But of course, there are some differences you should be aware of. So, let me show you the ins and outs of React Native code. We can check out how it looks and how it functions when compared to ReactJS to help you understand the similarities, but also the differences between the two. So, super quickly, you'll understand React Native's components and how to use them. When coding in React Native, you use JavaScript just like with React, but instead of rendering HTML elements, you'll be rendering native mobile components. Take a look at this basic React Native component. Here we're importing two important components from React Native library, view and text. Then we create a functional component called app that returns the text component wrapped inside of a view. What's interesting here is that we're using JSX syntax, which makes it super easy to create and visualize our components in a more HTML-ike way. But if you take a closer look, you can quickly see that this is neither a P tag nor an H tag, nor anything that we're used to while writing code that runs in the browser. In React Native, we use text instead. It's pretty straightforward. The text component is used to display text in the app and you can style it using the same CSS-l like syntax as in React. You can set the font size, color, and weight using the style prop. React Native also offers a stylesheet utility that allows you to define styles by creating a single JavaScript object. This is super handy in larger applications as it optimizes performance. But as we all know, Tailwind CSS is rising in popularity. So in React Native world, Nativewind came into the picture, allowing you to write Tailwindl like CSS styles in React Native. Isn't this crazy? It feels like you're writing a regular web app, but instead you're developing apps for mobile. Now, let's talk a bit about the view component. Think of it as a box or container that holds other components. Similar to the development in HTML, but with some added functionality specific to mobile apps. The view component is often used to create layout structures for other components. It has many different props that can be used to control its appearance and behavior. And one thing to note is that the view component uses flexbox layout by default, which makes it really easy to control how its children components are laid out. So, you can use flexbox properties like flex direction, justify content, and align items to achieve any layout you want. But what if you want to add some interactivity to your React Native apps? Well, get excited because there are some amazing components that do just that. Components for creating buttons, links, and other interactive elements. The first one on the list is touchable opacity, which is great if you want to create a simple button. Think of it like a cousin to a React button component, but with even more room for customization. And instead of an on click in React Native, you're not clicking it, you're pressing it. So, let's provide an on press. The second similar component is called a touchable highlight, which allows views to respond to touch in a unique way. When touched, the component reduces the opacity of the wrapped div, revealing the underlying color. And then there's the touchable without feedback, which is there if you need to create an element that is clickable, but you don't want it to have any visual feedback when pressed. It is super useful when creating links or images that don't need any additional effects. And apart from these touchable components, there's also activity indicator, which allows you to show a spinner or a loading indicator within your app. Sure, there's also a simple button allowing you to set properties like the title, color, and an on press that is called when the button is pressed, but whenever you need some more advanced styling or behavior, you'll find yourself using the touchable components much more often as they offer greater flexibility. Now, the next super important component on the list of components I want to teach you right now and which we'll use later on within our app is called a flat list. A flat list is perfect for rendering long lists of items that need to be scrolled. It's like the map function in React, but with some extra features like optimized scroll performance and item separation. The way it works is you of course import it from React Native and then you define some data such as an array of objects you want to map over. Then you put it all in a view and you call it flat list. A flat list by default already accepts some props such as the data prop to which you can pass an array of data you want to map over and then a render item prop which allows you to define exactly how you want to represent each item in the array. Pretty cool, right? Now, when should you use a flat list and when should you just map over the elements? Well, for larger lists where you want to have smooth scrolling, go for the flat list. While for the smaller lists, the map function will do the job. There's also something known as a scroll view. You can think of it like a magic box that can hold multiple components and views, providing you a scrolling container for them. It's like the Orflow scroll property of a div in HTML, allowing you to easily navigate through a list of items or large amounts of content in our app. You can put it within a view, call it scroll view, and then render some elements within it. By using the scroll view component, you can make sure that users can easily explore all the content, making your app more intuitive. And there's also a component which you'll find yourself using super often called safe area view, which gives you some sort of a safe zone to render your app's content within without it being covered by the devices hardware features like the notch, home indicator, or a status bar. It's great for building apps that are supported on different devices with different screen sizes and shapes. The way you use it is whenever you think something might be too long or awkwardly placed, you just wrap it within a safe area view. And while that default safe area view is amazing for most cases, sometimes it does fall short for some devices, making it not the optimal choice. So, I often like to use a package called React Native Safe Area Context, which works across all devices, even for the bottom bar. But you don't have to worry about remembering all of that right now. I'll show it to you once you start developing your own first app. But bear with me for a moment. I first want you to know which components exist so we can later on use them more easily. For example, we'll surely have to display some images within our app, right? So, how can we do that in React Native? Well, thankfully once again, it is super similar to how it looks like in React. You just use the image component provided by React Native. You pass the source to it, which can be a path or a URL, and you're good. But if you want to display an image as a background, then you should use the image background component, which works in the same way, but just renders it as a background. is specifically designed to allow other components to be layered on top of it. Whereas just the image is used for displaying images on their own. And both of these can handle different image formats like PGs, JPEGs, GIFs, and WEBPs. But none support SVG files because of some native rendering limitations. So if you want to use SVGs, there's a third party package called React Native SVG. Once again, don't try to remember it. I'll show it to you once again later on so you can use it within your own code. Okay, now that you know how to use images, what about modals? Well, thankfully React Native has components for that, too. It's called a model. Just import it from React Native, set visible to true, add an animation type, and define what happens when you close it. Pretty simple. There's also an alert component which you can again just very conveniently import from React Native and call it by saying alert. Provide a title and functions which you want to execute on cancel or on okay. And if you're creating forms you might want to create some kind of a toggle in React Native. And for that you can use a switch component. Once again it is super simple. Import it. Create a state for it. Same as in React. create a function that manages it and then simply call it like this within your code. Provide some colors, its value, and what happens when you click it. There's also a component that I use super often called the status bar. Both React Native and Expo have their own versions that allow you to control how status bar should look like for each screen within the app. I prefer using the one from Expo. You can define a view with some text and just beneath it, you can define a status bar. But what am I even trying to do? I'm just listing all of the different components that you can use to build your mobile applications. Well, I guess I'm just trying to show you that there are some differences like P tags and H tags are now becoming text. Divs are becoming views. But other than that, there are so many components that feel like you're writing React code within React Native, allowing you to build mobile applications. from touchable opacities for buttons to modals, alerts, and more. The list doesn't stop there. Of course, I don't want to waste your time. We're not going to go through every single component that exists in this video, but I did take some time and I wrote down the most important ones within the free React Native guide linked in the description. So, if you want to check them out there, see the differences and similarities, and just have a place where you can recap what you learned, that guide is what you need. But now that you know the most important components and concepts in React Native, let's dive right into action. I'll show you how to set up your app, what files and folders are involved, and everything else that matters step by step.
Project Setup
All right, you've got the fundamentals down. You understand how React Native works, how Expo simplifies everything, and what tools we're using to make this production ready. Now it's time to build recurly. And I'm not just going to show you how to build this one app. I'm going to teach you the full workflow from an empty folder to a published app on the app store so that you can use this process for any mobile application you want to build in the future. Once you understand this, you can ship anything. So let's start at the main source. ReactNative. dev. This is the official React Native documentation maintained by Meta. Head over to get started and you'll see something important right at the top. React Native allows developers who know React to create native apps. And then it says, we believe that the best way to experience React Native is through a framework. And then you can also use React Native without a framework. However, we found that most developers benefit from using a React Native framework like Expo. That's the React Native team telling you directly, "Use Expo. It provides file-based routing, highquality libraries, and plugins that let you modify native code without managing native files yourself. " So, that's exactly what we'll do. Head over to expo. dev. Expo is a full stack React Native framework with cloud services that cover every stage of the app cycle. Development, deployment, monitoring, and everything. Take a look at who's using it in production. There's Discord, Coinbase, Xbox, Burger King, and hundreds more. These aren't side projects. These are apps serving millions of users. It gives you tons of productionready libraries. camera, push notifications, biometrics, all tested across both platforms, so you don't have to fight with native modules. The developer experience here is built around speed. You develop directly on your phone using Expo Go. You can launch emulators without opening Android Studio or Xcode. Still, I'll teach you how to do that later on as it's needed to actually put this project up on both the app stores. So, before we write any code, let's get your Expo account set up. Click get started for free and create your account. It's completely free with no credit card required. Once you've set it up, we can create a new starter project, which will lead us to the instructions on how to do that. But you don't have to follow the steps here, at least not right now, because I'm going to walk you through the entire process step by step, starting all the way from creating an empty folder on your desktop and opening it up within your ID of choice. In this case, I'll be using WebStorm. So, simply drag and drop that empty folder in and we are ready to get started. If you want to follow along and see exactly what I'm seeing, I would recommend that you also download WebStorm as your IDE, as of recently it became completely free for non-commercial use, which means that you're getting a professional JavaScript and TypeScript IDE well for free. And with it, you can also set up Juni, your smart coding agent. We'll use it throughout this course to speed up our development. Not the fundamental parts where I want to teach you a lot of stuff about how React Native works, but the repetitive stuff, the stuff that just takes time and is boring to implement. Anyways, so you can go ahead and set up WebStorm and Juny right away. Once you're within your text editor or IDE, you can open up your integrated terminal and let's create our Expo application by running mpx create-expo@ latest-template. And we're going to use default at SDK-54. Now, why 54 when 55 is already out and Expo is going to add incremental upgrades to 56, 7, 8, and so on? While these upgrades are useful and they bring in some new features, what I care more about is for you to have the best experience following along with this course. I want you to learn as much as possible without running into any kind of version related obstacles. So follow along like this and then you can easily upgrade once you build the application and then add the dot slash at the end to create the app within the current folder. Press enter. It'll ask you whether you want to install the create expo app installer. So press Y to install it and it'll download and extract the project files and start setting up the project structure for you. So let's wait until it finishes. In less than a minute, our project is ready. And you can run either one of these three commands to run the project. Or you can head back over to the Expo website where we started a new project. And here you can see the second step, and that is to download the Expo Go application. And only after we have it, we'll run MPX Expo Start, allowing us to scan the QR code and connect to our computer. So, this is a great point for me to display my device right here on the right side. So, at any point in time later on throughout this course, you can see what I'm seeing directly on the phone that I'm holding in my hand. First things first, scan this QR code and download the Expo application for your phone. Then, back within the terminal, run MPX Expo Start. This will fire up a Metro bundler. It'll give you some kind of a QR code right here. and also additional options and shortcuts you can press. You can press the letter A to open in Android Studio, I for iOS simulator, R to reload, J to open up the debugger, and we'll use a few of these later throughout the build. But like I said, you don't need Android Studio or Xcode to get started because the easiest way to develop is with Expo Go on your phone. So once you download it, if you're on iOS, head over to the camera app and simply scan the QR code and press open in Expo Go. This will immediately run your application directly on your phone. And you can see it right here. This is the developer menu. You can shake your device or long press anywhere on the screen with three fingers to go back to it at any point in time. And then you can see all the additional settings. So once again, if you want to visit it, just shake your phone in your hand, literally, or press and hold with three fingers and it'll open up. And that's it. We have a fully functional mobile app running on our phone in a couple of seconds. If you're on Android, you'll have to scan the QR code directly from the Expo Go app. And if you're on the iOS and it's not connecting, make sure that you're on the same network as your computer and that your VPN in settings is turned off. So, go to settings, search for VPN, and make sure that it says not connected. And also make sure that you're connected to the same Wi-Fi network as your computer. And if you do that, it should all work. And what you have here is your first React Native app running on a real device, which took about 2 minutes. But now, let's understand what Expo generated for us. I'll go from top to bottom and cover the most important file. First, we have the package. json, which is your project's manifest. The key line here is the main part where it says expo router entry. This tells Expo to boot the app through the Expo router filebased routing instead of manually written app. dsx. Then you have your scripts right here, starting with the start script. Then you have scripts for Android, iOS, web, lint, and finally the reset project script which we'll use in a moment. Then there's the app. json file which is expose configuration file. It controls your app name, icon, splash screen, orientation and also platform specific settings for iOS and Android. We'll come back to this later on when we configure our app assets and set up EAS for deployment. For now, you just need to know what it does. There's also the tsconfig. json file right here at the bottom. This one extends the expose base TypeScript config. And two things matter here. Strict is enabled which catches more bugs at compile time and the path aliases that we said so that we can import with add slash instead of using the full relative path like dot dot slash and so on. So this is going to be super handy. Then there's the package log JSON which locks exact dependency version. get ignore which keeps node modules expo cache and build artifacts out of the version control. There's also the scripts folder which has utility scripts within them. The assets folder which holds your images, icons, splash screens and fonts. And now we dive to the most important parts. The real code lives within your app folder. Within it, you can see all of the different routes. First, there's the underscore layout file, which is the root layout that wraps all of your screens. Right now, the only thing it does is it renders the tab navigation with two tabs that you can see at the bottom, which changes the screen. Then there's the index. tsx, which is the first thing users see when they go to the forward/ route. There's also the second page, which is the explore page. If you click it on the tab navigation at the bottom and also under components, you'll be able to see all the components and reusable pieces of UI like the animated pieces of text, scroll views, and more. But that's a lot of starter code that we don't need. We're building things from scratch. So, let's clean the house. Open up your terminal. And I'll actually open up a second one. This one I'll call expo. And a new one, which I'll call simply terminal. And within it, I'll run mpm run reset project, which is one of the scripts within their package. json that Expo created for us. This script will ask you whether you want to remove the existing code or you want to move it to an example folder instead. I'll simply move it so you can refer to it later on by pressing Y. Expo Go would now like to access your motion and fitness activity, which I'll give it access to. And the project is reset. The next steps are to rerun the MPX expo start. So I'll simply stop it from running in the initial terminal by pressing Ctrl + C and then run MPX expo start. This will simply restart it. And again on your phone you can hold it with three fingers at the same time and then tap reload. As soon as you do that you'll see that now we have a fully empty version of our application that simply says edit the app index DSX to edit this screen. And there is absolutely nothing else here. So this is our starting point and everything we build from here is recurly your full stack subscription management application.
Setup Styling (Nativewind)
application. Next let's set up styling. We're using Nativewind which lets you write Tailwind CSS classes directly in React Native. So if you've used Tailwind on the web, you already know how to style mobile apps too. same utility classes and same mental model. So head over to nativewind. dev and we'll be using the latest version of nativewind v5 which uses the latest Tailwind CSS features. It might still be in pre-release by the time you're watching this, but the core principles, the classes, and the workflows are stable. If anything changes, it'll be minor file paths or configs, not the way you write styles. So head over to get started. And here you can see the installation steps. But instead of following this v4 version, we're going to switch over to native wind v5, which is the latest version. And then you can go to the installation page and follow these steps right here. If we were starting from a new project, you could have automatically set up a new expo project with native windv5, expo 54, and tailwind CSS. But since we have an existing project, let's do it step by step. I'll put my terminal right here on the right side so that we can more easily go through the steps. The first step is to install native wind and its peer dependencies. Tailwind CSS, React Native Reanimated and React Native Safe Area View. And we can do that either with Expo or through MPM. In this case, let's proceed with Expo by running mpx expo install and then install all of these additional dependencies which are essentially going to do the same thing. Simply run mpm install and add them. There we go. Now moving on to the second step which is to set up Tailwind CSS. We'll copy and paste this command. Optionally we can also install prettier plug-in Tailwind as a dev dependencies so we can format our code. And we need to add Tailwind to our post CSS config. So we need to create a new postcss. config. mjs which I'll do right here in the root of our application. Create a new file called postcss. config. mjs. We can automatically add it to git. And then we can simply copy and paste this block of code within it. We also need to create a globals. css CSS file or global. css and add the following tailing directives. We already have the global file. No, I thought we had but we don't yet. So, let's create a new file called global. css and simply paste these four lines right into it. From here onward, replace global with the relative path to the CSS file you just created. Okay, so that is this global. css file. Now we need to create or modify our metro config. I don't see that we have that metro config yet. So we can run this command right here. That's going to be mpx expo customize metro. config. js to create that file. Soon enough it'll be generated and we'll be able to modify it. So simply we can replace it with what we can copy over from here in native wind. And then we can also import our CSS file within either our app or within our layout. So if you head over into our app layout. tsx here, we're importing everything. So it might be a good idea to just import the at /global. css file right here for the styles to take effect. We also need to force lightning CSS to a specific version in our package JSON. So let's copy this. head over into the package. json file and under overrides which are going to be at the bottom or we can add them right here below the dev dependencies. We're going to set the lightning CSS to 131 as they specify right here for you. These steps might be a bit different but it's important that you follow along with exactly what they have. And we are using TypeScript. So we have to create a new file called native wind- env. d. ts also at the root of the application. That's going to be native wind- env. ds. And we have to just add this reference right here. This file should not be edited and should be committed with your source code. Finally, we are ready to try it out by copying this updated app. tsx. Moving over to our app index. tsx and replicating everything we had with this new welcome to native wind part. It should show how class name props can be used tailwind utility classes color utilities and also typographies. And if you see the style text centered on a white background, native width is working properly. So let's test it out by bringing it right here. Immediately you'll see that the changes are not yet applied which is fine. That means that we have to restart our application. So I will simply press the letter R to do a manual reload first and then we'll get a little error saying that it cannot find the global. css file. So let's just fix this path by pointing to at/global. css and then run r to reload one more time. And now we can see welcome to native wind. Although the styles are not applied, this text isn't centered in the middle of the view, and we don't have a background or a blue text. So, what I'm going to do instead is stop the server by pressing command C, and then rerun it again with MPX Expo Start and then press the letter R in the terminal to rerun it one more time. This issue is most likely connected to, yep, I think I found it. Lightning CSS, which is one of those uh version mismatches that the docs warned us about. We added this override for it right here within package. json at the bottom, but we never reinstalled after adding it. The override only takes effect on a fresh install. So, what we need to do is delete the node modules as well as the package lock JSON and then reinstall. I'll do that by opening up the second terminal and running rm-rf which means delete folder and file node modules and package lock json or you can delete them by right-clicking and then clicking delete. Once that is done, simply run mpm install one more time to reinstall our dependencies. Let's let that run. And after that is done, simply head back over to Expo, stop it from running and run MPX expo start with a d-clear flag, which will restart Metro with a clear cache. And then press enter. And then finally, the letter R to reload the app. As soon as it rebuilds it, you'll be able to see a blank screen with a center text that says, "Welcome to Native Wind. " exactly as we expected. Now, currently we have a blank slate and we can use all of these Tailwind utility classes which already makes the development of this app so much simpler because we don't have to have separate CSS files and so on. But one way to improve it even more styling wise is to form some kind of a theme for our application. For that reason, in the video kit link down in the description, I'll share the access to the subscription management app design on Figma. It looks something like this. It has very bold colors and allows you to budget a bit better. So, from here, we would need to extract specific colors like this huge primary color E87A53 orang-ish. Then, we have this papery like background color right here. and then some accent colors too. Instead of extracting all of these manually, in that same video kit link down in the description, I'll share with you the final global. css file. It'll include this tail and CSS imports, but also a couple of components for the tabs and some spacings so we can have uniform space within our application. And most importantly, the color, the background, the foreground, even some primary colors, the accent, the one that we saw, and colors for success, destruction, and subscription. Perfect. This is it. So, simply copy and paste that file here. In simple words, this is basically Recurly's design system. So, let's test it out by heading back over within our index. tsx. And we can now try using these custom color codes. For example, instead of BG white, I'll set it to BG background. And instead of text-b blue 600, we'll set it to text- success. So now if you save this, you can see that everything changes instantly, which means that we have successfully set up and installed native wind, tailwind utilities, and a custom color system that matches our Figma design. So from here on, every component we build will use these variables. Nice. So now when you start creating your new applications in the future, you know how to style them, too. Oh, and a pro tip. If you're starting with a new project and you already know that you're going to be using native wind from the start, you don't have to follow all of these steps. You can just install and set up everything from a single command right here. With that in mind, the styling setup is done. And in the next lesson, we can dive right into the routing and navigation of our application.
Routing & Navigation
As I mentioned in the crash course, routing in React Native with Expo Router works almost identically to Nex. js. The file name becomes the route path. No manual navigation config, no route arrays, and no wiring together by hand. So, in this app, every file inside of the app folder will become part of the navigation tree. But unlike a single demo app, our root route isn't the full home screen. It's an entry point that decides where the user goes. So, back within our application, we have this app index. tsx, the starting route that's already set up. Now, let's prove how easy it is to add a new one. Say we want to add an onboarding screen. Simply create a new file right here called onboarding. tsx. Run rf, which is a React native function with export. And this will quickly spin up a new functional component with a view and a text. That's it. Your onboarding screen now exists at the forward/ onboarding path out of the box. To test it, you can go back to your homepage and add a link. I'll do that right here by saying link, which we can import from expo router with an href pointing to onboarding. And we can also give it a class name set to margin top of four rounded bg primary text- white and P4. It's going to be like a button that'll say go to on boarding. If you save it, you'll be able to see it right below. And if you press it on your phone, you'll be navigated over to the onboarding. And the best part is you can use your devices native capabilities to go back. Since I'm on an iPhone, it's natural for me to just scroll from left to right to go back. That's the core idea of Expo Router. create a file, it becomes a route, and you navigate to it with a link. Same pattern you'd use in Nex. js. But now, let's think about this in terms of a real application. Open up the Figma design. As you can see, we've got onboarding screens. There also might be some O screens and then main app screens with tabs and detail pages. If we throw all of these files directly inside of the app, the project will get messy pretty fast, 10, 15 files all at the same level with no organization. Expo Router solves this with route groups. A route group is a folder wrapped in parenthesis, which lets you organize related screens into folders without adding that folder name to the URL. So let me show you how to implement it for authentication. We'll start by creating a new directory in the app folder and start with parenthesis and end with a closing parenthesis and call it O. Then within O create two new files sign-in. tsx tsx where you can run rf and this one will be called sign in and it'll have a link component which is going to be pointing to o like this. So forward slash route group and then forward slash signup and this link will say create account. Now I will copy this entire page and create another file which is going to be called I think you can guess it sign dashup. tsx where you can simply paste it and rename this from sign in to sign up. Change this text right here to sign up and then instead of create an account you can simply say sign in. And because O folder is wrapped in parenthesis, the word O will never appear in the URL. Well, if you were in the browser, uh these routes will resolve directly to sign in and signup pages, not O/signin. These parentheses are purely for organization. The URL stays clean. For now, these are placeholder screens as we're learning the navigation structure, not building the UI yet. But let's just verify they work by updating our homepage and adding two new links. I'll create one right here. And actually, I'll just go ahead and duplicate the existing one we have two times. And this one will say go to signin pointing toward slash off in parenthesis slash signin. And then the second one will go to sign up and it'll simply say go to sign up. Perfect. So now if you save it, you'll be able to see three buttons. So if you click on sign in, you'll see unmatched route page could not be found. So what we can do is reload the page and try to go to sign in. And now you can see that it works. This is a another pro tip that you might encounter needing often and that is that even though your code is 100% correct and everything should be working, either it's going to show you an error or the right thing isn't going to show up on the screen yet. Um, and when that happens, you just need to press the letter R in your keyboard to reload it or hold three fingers on your device and then reload from here. Either way is totally fine. And sometimes there's going to be cases where even that is not going to fix it. So then you'll have to press Ctrl + C and restart the entire server, maybe with a clear flag to clear the cache. But more on that later. For now, we have three fully functional routes. And you also learned about the concept of route groups, which we can use for organization. But then we also have something known as layouts. And the concept of a layout is directly connected to group. For example, if I go to onboarding, you'll see that it'll just say onboarding at the top because it is at the root route. But if I go to signin, you'll see o and then sign in. That's because expo router uses stack navigation by default and each layout adds its own header. So just like this root layout wraps all the routes, we can also create a layout specifically for the o group. So head over to the O and create a new file called underscore layout. tsx. Within it, we can practically duplicate this existing layout with a few modifications. Instead of returning the stack empty, we'll return it with an additional screen options property where we'll set the header shown to false. And as soon as you do this and head over into signin, we were hoping to see the header completely hidden. But that isn't really in the case. What is the case is that without it, you would have two different headers. The one from the homepage and then the one coming from the second layout. So this one only hides one, the header for the odd screens. But you'll still see the root layouts header on top. We can hide that too by also applying the same property right here of screen options shown set to false. And now we get a nice blank screen. Very clean. The important takeaway here is that every screen inside of the O now shares this O layout. Later when we build real O UI, this layout is where we'll add shared styling, background colors, or animations that apply to all O screens. And finally, let's talk about tabs, specifically bottom navigation. If you check the design, you'll see that we have a couple of tabs right here at the bottom. One is pointing to the homepage. Then there's the my subscriptions page. Then there are some additional insights and then optional settings. These four screens need bottom tab navigation. So back within our code, let's create another route group. Route group is simply a folder wrapped in parenthesis. So that's going to be called tabs like this. And it's going to be within the app folder, not within the O folder. Then inside it, create a new file called subscriptions. tsx tsx where you can run rnf to quickly spin that component up and then we'll create another one which is going to be called insights. tsx tsx also run rnf and finally also add the settings page. So right here settings dot tsx and run rnf. This gives us the three pages that we'll later on implement. And don't forget that we also have the home screen which is our index. tsx. Also move it inside of the tabs folder. It'll still function as the homepage since index. tsx tsx always maps to the root of its group. So again, since we don't have an index here, it's going to take the one from here, but now it's part of the tab structure. So this is a super useful routing pattern. You've got your app level entry point at the root, which is this layout right here, and then your o screens are grouped together because in most situations, they don't need the bottom navigation. But then your main application screens are grouped under tabs so that each group can have its own layout, its own navigation behavior, and its own shared UI all organized within folders. Oh, and one more thing, there are also these so-called dynamic routes. So, if you need a subscription details screen, you don't want the tab bar showing that on the page because there's going to be hundreds of different subscriptions and we can have a tab button for each one. Instead, we want to create it outside of the tabs group as a dynamic route. So, head over into tabs where we have subscriptions and create a new folder of subscriptions this time. And within the subscriptions folder, create a new file wrapped in square brackets and make it called ID like this. but ID ending and tsx because that's also its own page. I'm not quite sure why there's a snail here, but I'll take it. Maybe if I recreed, it's not going to show a snail. Nope, the snail is still there. That's totally okay. So, the bracket syntax works in the same way like in Nex. js. So, if somebody goes to subscriptions and then a specific number, this component will show. So, let's quickly spin it up by running. We'll call this one subscription details. And don't forget to export it at the bottom. And here the text can say also subscription details. But the question is for which ID. How can we figure out which ID is this user checking out? How do we get this ID variable? It's super simple. Once again, you just dstructure the ID coming from use local search params coming from expo router. And you can even define its type such as an ID of a type string. In this case, that's going to be like this. And I'll also add another link component right here to go back to the previous page. So that's going to be href pointing to the homepage. And it'll say go back. Now we can add these links to test from the homepage. So heading back over to our tabs index. tsx, tsx. Right below these links, we can now add two more. One link pointing to a forward slashsubscriptions/spotify which is going to be for the Spotify subscription. And in the other one, you can do it dynamically like this pointing to a path name and then passing the param of ID like this. Both of these are going to be valid. So now if you reload the app, we're going to be back at the homepage. And if you click on Spotify subscription, right at the top, maybe barely, you'll be able to see a go back button and then a Spotify subscription, which means that it's loading it. But hey, why is all of our text flowing up? This is the most common issue in React Native applications. And that is making sure to contain all of the contents of the application within the specific devices boundary. For example, here on iPhone, the time is shown on top left. And then there's this dynamic island as well. So, we want to make sure to use a safe area view to contain all the content within the safe boundary. I'll teach you how to do that very soon. But yeah, take a moment to think about what you've just built. Four different concepts that power the entire navigation of this app. Starting with files to create routes. You create a file and it becomes a screen. Route groups to organize screens without changing the URL and parenthesis to keep things clean. Then layouts that let multiple screens share the same navigation parent. Basically, every screen in a group inherits its layout. Then there's the bottom tabs navigation, the dynamic routes that let one file handle many detail pages. Don't forget to use the bracket syntax in the file and then the navigation links to actually move between pages. So you're not building a massive navigation configuration and then attaching screens to it. You're building the navigation tree directly through folders and files. And that's the real power of Expo Router. At this point, I think this codebase deserves to be pushed over to GitHub. It's something you should get into the habit of doing as early as possible as the application grows so that you can safely implement new features and go back if you need to. So, let's go ahead and push it by first creating a new repository on GitHub. You can do that by heading over to github. com/new. I'll add it to my own profile and call it react native recurly which is the name of our application. Then simply create a new repo and we can follow the steps to push it. You can either do that manually by following all the steps or you can copy it and we can outsource this work to an AI agent. You can use any AI agent you prefer, but in this case, I'll open up Juny powered by Jet Brains's AI chat, which allows you to complete code, generate commit messages, generate tests, ask questions, and so on. And you can even use it to get explanations of specific code, which is super useful as you're following along with this tutorial. If I don't explicitly explain something, you can use it to give you the explanations so you can learn better. But yeah, it allows you to choose any AI agent out there. I'll set it to auto and turn on the brave mode, which allows the agents to execute terminal commands without confirmation. And then I will simply paste this part right here and press enter. It should now go through all of these commands and push the code over for me to GitHub. It's going to do it command by command and it's going to even write nice commit messages. And there we go. in about what like 5 seconds if you reload your GitHub. Again, you could have totally manually went through all these steps to also push the code. Now, when we're here, I like to do a bit of a cleanup. Uh we can give this project a bit of a description. I'll say recurly subscription management React Native application. For the website, I'll enter jsmastery. com for now. And for the topics, we'll do React Native as well as expo. And we don't need to show these on the homepage. that's going to make your repo that much cleaner and allow us to push new features more consistently. Now, one of the primary reasons that makes me push to GitHub early on nowadays is the fact that we can then get Code Rabbit to analyze our code. Code Rabbit is an AI code reviewer that reviews your code and flags any issues. So, it's super useful to have it early on so that if something goes wrong, we can fix it. It also has a hidden use case and that is that it teaches you how to write better senior like developer code because it flags things on a senior level so that when you fix it once the next time you're not going to make that mistake in the first place which means that you're going to be that much better of a developer. So click the link down in the description try it for free and connect over with GitHub. It'll load up your workspace and then you'll have to connect it by heading over to add repositories and connect over with your GitHub. Once you do, give it access to your repos. And then if you switch to your right account and search for the name of your repo, you should be able to find it right here. And you can make some changes to how it reviews the code. But I don't personally change anything here because it reviews it well by default. So since this was just a demo PR, there's nothing to review. But from here onward, whenever we develop a specific feature, we'll do it how it needs to be done in production, which means that we'll open up a pull request for it, review it, and only if it's good, merge it to main. Let's do that immediately in the next lesson, starting with removing these fake links and turning them into a bottom tab navigation.
Tab Navigation
navigation. In the last lesson, you created the route files for Recurly and grouped the main screens inside of the tabs group. That means that you already have the homepage, the subscriptions page, the insights page, and the settings page. Now, we need to connect them with a bottom tab navigation. Just like we defined the root stack inside of the layout. tsx, we now need to create a layout specifically for the tabs group. So, head over to tabs and create a new layout file layout. tsx. and let's create it together from scratch. We'll start by creating a new functional component called tab layout and make it equal to a react component. Then from within it, we can automatically return not a div. We don't have divs anymore. We're now working within web. But we'll return tabs. Tabs are going to be coming from expo router and we need to close them. If you used curly braces here, in that case you'll need to add a return statement here. But in this case, since we will be automatically returning it, there's no need to explicitly add a return. We can just use the parenthesis which mean auto return. So whenever you see this later on, you know the difference. So for these tabs, we can also apply a screen options of header shown is set to false. And within it, we are ready to display our screens by saying tabs. creen. You give it a name such as index. And you give it an options. So you can redefine its title. We don't want it to be just index. We home. And of course, don't forget to export default the tab layout. If you do that and reload the application, you'll now be able to see a bottom tab with all of these new pages. If you remove the header shown, you'll also be able to see it at the top. And now, because we use this option title home, it doesn't say index, which is the regular file name, but you can actually name it with a first capital letter. And I'll bring back the screen options to remove the header. Great. So now you can also see how home has a title at the bottom and inside settings and all of these just have their file name. So, let's recreate all of these different routes. We'll have 1 2 3 4 five I think in total. The second one is going to be called subscriptions. And the title will be the same word but with a capital letter. Then we'll have the insights. Same thing for the title. Finally, we'll have the settings and right here. I was wrong. In total, we just need to have four and not five, which means I can remove the latest one. And now we have what appears to be a nicel looking bottom navigation. And even though we want to have just four, it seems like there's a fifth tab right here pointing to the subscription details page. This one belongs to this dynamic route, which I didn't want to have the tab for. So since we don't want to have a bottom tab navigation for it, we can just bring it back to the root route so that it exists and we can route to it but it is not part of the tab navigation. So now if you reload expose routing system autorecognizes it. So we can specifically set it to null by duplicating tabs. screen screen specify the route to subscriptions and then forward slash ID like this and then set the options of href to null like this which will then simply hide it from the bottom navigation bar. So what we've done here is that each tabscreen maps directly to one of the tabs within the tabs folder. Index points to index, subscriptions to subscriptions and so on. And the title is simply what shows up in the label as the label in the tab bar. So now you can already and successfully switch between those different pages. But now let's make it look better. Before we customize the top bar, we need to set up a couple of things. I'll provide you with the icons and the constants. You can grab these from the video kit link down in the description. That'll look something like this. And then at the bottom, you'll be able to see the codebase and the assets. So, click on the assets and then download this zipped assets folder from Google Drive. Once you download it, simply unzip it and then replace the existing assets folder by deleting it first and then simply drag and drop the new one right in. This folder will contain all of the icons and images we'll use throughout the rest of this application. Once you have the assets, let's create a file that uses them. I'll first create a folder called constants. And within constants, I'll create a new icons. ts file. You can find this file in the video kit link down in the description. Simply copy it and then paste it right here. You'll see what we're doing here is essentially we're just importing all of the assets and then exporting them so that we can more easily use them as icons. It seems to be complaining about the path. So, let's see why that is. It says that it cannot find it even though when I click on it, it works. This pattern will help us keep all of the icons centralized. So instead of importing them from the asset path, you can just import them from constants icons and get type safety throughout the icon type. But by default, TypeScript doesn't know how to handle image imports. So let's create a new file in the root of the application and call it image d. ts. D as in declaration. And I'll also provide it in the video kit link down in the description where we can tell it to understand how to read these image imports. So with that, this file will no longer complain. We'll also add another file at the root of the application called type. d. ts where we can declare all of the other types starting with the images. And you can pause the screen and copy it out, but basically we're telling it that we're working with images. And finally within the constants. So that's the same folder where we have the icons. Also create another file called data. ts. Within data we can import all of the icons coming from icons and then export different tabs which is going to be an array of tabs. Here we can create different objects containing a name such as index, title such as home and also we can provide an icon pointing to icons. home. You can duplicate this three times. The second one is going to be subscriptions. I mean you already know this because we've done something similar in the homepage which will replace with this icons is going to show to wallet. Then we have insights. The icon is going to show the activity. And finally, we'll have the settings with a title of settings pointing to icon. Setting. Now, before we go ahead and use these, we'll also want to modify the styles of our tab bar. And for that we'll need a tiny little package called clsx which you can install by running mpmi clsx which allows us to join different classes together. Then within the tab layout let's go ahead and update it. We no longer want to display these tabs one by one. What we want to do instead is just display the tabs like these and then map over the tabs coming from data. ts. And for each one of these, we want to return a tab. screen. So that's going to look like this. Tabscreen. Same as we've had before, but now we're doing it dynamically where we can pass a name equal to tab. name. We can also pass a key since we're mapping over it such as tab. name. We can pass the additional options. In this case, that's going to be the title coming from tab. title. And we can also pass the specific icon. So I'll put this into a new line so we can see it a bit better. And under options, you can provide a tab bar icon which is going to be different depending on whether it's focused or not focused or clicked or not clicked. So here we'll create a new little component within the tab layout component that I'll call tab icon. And it'll take in two different things. the focus state and the icon itself which are going to be of a type tab icon props. And now we can automatically return a view which in web world would be just a div. Don't forget to import this view. Now the reason this is red is because we used an immediate return but instead we want to use curly braces so that we can contain this within it and then explicitly return this part right here. Once you fix that, it'll no longer complain. And we can give this view a class name of tabs dash icon. Within it, we want to display those icons. So, I'll render another view with a class name set to dynamic property of clsx, which is the package we installed and which we have to import. And then it'll always render the tabs-pill class name which will make it rounded and properly positioned but only when it is focused. We also want to give it a class name of tabs-active and within it we want to display an image with a source pointing to the icon dynamic icon we want to display with a resize mode set to contain as well as a class name set to tabs dash glyph which is coming from CSS and basically it's going to apply a size of six to it and it looks like resize mode has been deprecated so I'll try not passing it in. Now we can use this tab icon right here under tab bar icon as a component and to it we can provide a focus state set to focused and the dynamic icon which is going to be set to tab icon. Add a comma here so we don't have any issues. And let's reload the application to be able to see the changes. It'll try to get access to the first asset that we have right here under constants coming from icons. But it says that it cannot find it by trying to import activity. png. Even though it seems to be here, I'll rerun the entire application, the server, by running mpx expo start. And I don't think we'll need clear this time. I just want to restart it because maybe these files didn't yet appear. And as you can see, I was kind of right because that allowed us to go past this error that we were initially seeing. So when developing React Native applications, trust yourself more. Maybe just the metro bundler is messing with you. So with that in mind, we can now see something interesting at the bottom. Not quite yet icons, but something is happening. Let's style it further because then it's going to look better. So under tabs where we have the screen options, we'll provide more properties within these screen options besides just header shown. I'll also add a tab bar show label and set it to false. We don't need to say home when we can show the home icon. I'll also modify the tab bar style by setting its position to be absolute. But even with this position absolute, it's not looking good. Here's the catch. On newer iPhones, there's a home indicator bar at the bottom. And on older devices, there isn't. So, if we hardcode a bottom value, the tab bar will either overlap the home indicator on new phones or float too high on the old ones. And that's where we want to use React Native safe area context. It's a super popular library with over 4 million weekly downloads, which basically handles those edge cases. So we can install it by typing this into the terminal. MPMI react native safe area context. And once you install it, you can just import use safe area insets coming from React Native safe area context. And then you can get the insets like this by simply calling it as a hook. This will tell you exactly how much space the devices hardware elements take up. The bottom inset on an iPhone 15 is around 34 pixels, but on an older iPhone with the home button, it's zero. And we can use that value to position the tab bar accordingly. So right here below position absolute, we can now use a math. max and choose from insets. bottom. And then we need to pass a horizontal insert right here. So create a new file under constants and call it theme. ts. This theme you can get from the video kit link down in the description, but essentially it is nothing else than what we already have in the globals, just in the JavaScript format, so we can use it within our components. We have different colors right here, different spacings and different components or specifically the tab bar values for specific components. So once you have that you can now get access to it right here at the top by saying tab bar is equal to components tabbar and these components are coming from the theme. So you can import them as colors and components coming from add slash constants slash theme and then here under insets bottom we can also add those custom values tabbar horizontal inset. Now let's reload the app and you'll see that we have a little error right here under math. mmax. That's because here we're basically changing the bottom position. So that's what this math max has been changing all this time. So if you specify here that the bottom is for the mad do. mmax and reload you can see that now we moved it a bit above. Let's also add a height of the bar which is going to be tabbar. height. We can also add a margin horizontal which is going to be tabbar. h horizontal insert. We can add a border radius tabar. And this whole time we're trying to make the bar appear floating. Right? If it was just laying down at the bottom like it used to, that's fine. But the floating bar requires some work. Let's also change the background color to colors primary. There we go. Now you can see why we imported those colors so we can use them super simply like this. And then let's also change the border top width which is going to be set to zero as well as the elevation well. So to center it, we can go outside of the tab bar style and modify the tab bar item style. And that's going to be padding vertical set to tab bar. / 2 minus the tab baricon frame divided by 1. 6. I found this value to work the best, but we can play with it later on. And finally, we want to modify the tab bar icon style. We're going to set its width to tab bar icon frame, the height and align itself set to center. So now you can see that it is centered, but unfortunately we still can't see the actual icons. So let's see what that is all about. Under options, we're displaying the tab icon and passing the two different states. Then on top we have this tab icon where we're taking in the focus state and the icon itself. And we are returning a view that says tabs icon with a view class name tab spill and when focused also tabs active. And we're passing an image within it with a source of icon and a class name of tabs glyph. It's all looking good. But then why can't we see the image? Oh, it's coming from Expo image, but it should be coming from React Native itself because that's a base React Native component. So, if we fix this, you can see that now it works. And now that resize mode is not going to be deprecated anymore. We're working with a completely different component. So, take a look at that. You've just implemented this amazing floating bar navigation, and it's super fun to click around it. So, if you test the app a bit, you'll notice that the navigation is going to work, but the content on all the screens gets cut off behind the tab bar or the status bar at the top. So, let me show you how we can fix that. It's super simple. You just have to go to the tabs index. tsx and we have to wrap this part with a safe area view. So, instead of a regular view with these classes, use something known as a safe area view. coming from React Native safe area context, not from React Native. So right here, I'll say import safe area view from React Native safe area context. Let's give it a class name set to flex-1 bg background and a padding of five. And it won't accept the class names because we have to turn on the styled component. And to do that you have to import it under a different name like RN safe area view. And then you have to declare it like this. Safe area view is equal to styled which is coming from native wind RN safe area view. And this will allow you to pass the styling. And this styled part is coming from native wind. So you can just import it right here. There we go. Now you can see that the styling will get applied. The only reason why we have to do this is because the safe area view from the react native safe area context is a third party component and native wind needs the styled wrapper to enable class name support on it. And now you want to apply the same pattern to all three other pages. So I'll simply put this here. Copy these three lines and also add them to insights right here at the top. that's going to be safe area view and replace it here. So now if you add some additional class names and go to it, the insights will appear nicely within the screen. So let's copy it one more time. This time to the setting page. So also take it here. That's going to be settings and replace it with a safe area view with some class names. So the setting page gets fixed as well. And then finally, we have the last one, which is going to be the subscriptions page. So, we can paste the same imports right here. That's going to be these three lines. And then we can just use a safe area view instead of a regular view on the subscriptions page as well. We have our homepage with some navigation. the onboarding and the two odd screens haven't yet been set up properly, but all the pages within the tabs group and the tab layout itself works flawlessly and is already starting to feel like a real mobile app. So that's it. You now have a custom bottom tab navigator with themed icons, active states, and proper safe area handling for all the screens. Most importantly, you have a clean mental model for exactly how to build this tab navigation. We define our screens as files, then configure them through the data array and then import and map over them and define the styles directly within the tabs component. Next up, we'll build the onboarding screen. That's super useful, our first real screen. But before that, let's make sure our code is written well. So, let's push our code and we'll push it to a dev branch. A branch where we're going to be checking our code on before we merge it to main. Run git checkout dashb to create a new branch and switch to it called dev. Then run git add dot to add all the changes. And let's commit the changes by running something like git commit-m feat as in features we implemented project setup native wind routing and tab navigation. Press enter. We've done a lot already. And then run get push u origin dev. This is going to push the changes to the new branch. So now if you head back over to GitHub, you'll see that your repo and specifically your dev branch had recent pushes about 10 seconds ago. So compare them and open up a pull request and simply create it. By itself, we didn't really feel the need to add a large title or a description. But now, if somebody was reviewing this, there's a lot of files that they would need to review, some of which we implemented, most of which are actually just assets. So, it's good to have an AI tell the person reviewing, "Hey, here's what you can check out, like new features and some UI and style updates. You can check that out yourself, but instead let me provide you with a full AI generated review. So, let's give Code Rabbit about a minute to untangle our code like a pair of headphones. And the review is in. You get a quick walkthrough of everything we implemented together so far. A new tab-based navigation system introduced using Expo Router's tabs component with a reusable tab icon styling. We have constants that define the tab configuration, icon mappings, and design tokens. And we also use these four existing screens with safe area view and native wind styling. So let's go ahead and take a look at the changes. You get some nitpick comments which you can fix if you want to. Most often this is just about removing unused imports. And then let's take a look at the real review. Here it says that we have the incorrect route path that'll cause navigation failure. Specifically, it's referring to our dynamic route pointing to Spotify. Oh, that might be true because we changed its position, remember? So, if I go to Spotify, it really does break. So, this was a great catch by Code Rabbit. Then, you're given a couple of different ways to fix it. First, it's just a simple copy and paste fix. You can take this line of code, head back over to our tabs index. tsx, and just replace it right here with the right one. And immediately if we go back and revisit it again, it's going to work perfect. Other ways include an auto commit, which means that you can immediately just press commit changes and it'll push it to your codebase, which is great, and then prompt for an AI agent to fix in case more changes are needed across multiple files. But great, we got this one fixed. And we only have one other change needed right here, and that is to remove the unused view imports. Not a big deal for now, but it's always great to keep your code clean. So I'll remove it from all the files where we referenced it. It's going to be mostly within all of our screens. So that's going to be here, and here. And with that, we can go ahead and push those little fixes. So I'll type git add dot g get commit-m implement fixes and run g get push. Automatically github and code rabbit will recognize these new changes and we can just go ahead and merge this BR. Sure enough these fixes were super simple because the code so far is simple. But as we move into business logic and more complicated functionalities you'll see just how more detailed code rabbit will get. Great.
Custom Fonts
Next up, typography. Default system fonts work fine for prototyping, but once you want your app to feel like a finished product, the font matters. It's one of the first things that your users notice subconsciously, even if they can't articulate why one app feels more polished over the other. For Recurly, we'll use Plus Jakarta Sans in multiple different weights, from light to bold. It's clean, modern, and it works well on mobile screens. I get most of my fonts directly from Google Fonts. You can just go here, search for the one you like, and then just download it. You can select different variants, and then download it, and you'll get a zip with TTF files. Now, on the web, using this font is super straightforward. You link a Google font or just add a font face rule, and you're done. But React Native doesn't work that way. The runtime doesn't automatically know about the files sitting in your assets folder. You need to explicitly load them before any UI renders. And that's what Expo font handles. It's already installed in your project since it comes with the default Expo template. We just need to use it. So there are two steps. First, register the fonts in your app config and then load them in your root layout before rendering anything. In this case, you don't have to download the fonts as I provided them to you right here under assets fonts. So, the only thing we have to do is head over into our app. json. And here we'll add an expo font plugin to the plugins array. So, search for plugins. And here you can see expo router. We have the expo splash screen below it. And then right after the splash screen, we'll also add the expo font, but it has to be in a new array. The first thing within the array is the actual plug-in that you want to add. And then as the second param, you can provide an object with additional options such as fonts. And then you can define this as an array where you can start listing out different fonts such as slash assets slash fonts slash plus jakarta sands regular. ttf and we'll actually implement a couple more like bold medium. We'll also do semibold. There's also an extra bold which we can use for a very heavy titles. And then we'll also need a light. Perfect. Make sure to add commas on all the lines besides the last one because we are in a JSON file. And that's it. This tells Expo which font files exist in the project. But registering them here isn't the same as loading them. They're not yet available for us to use. So step two is to load the fonts. So head back into our app layout. tsx. tsx this one right here. And we need to load the fonts before any screens render as we need to keep the splash screen visible while they load. So if we don't, users would see a flash of unstyled text before the fonts kick in. So that's why we're going to do it right here. So right at the top of the root layout, I'll say const and do array dstructuring fonts loaded and use the use fonts functionality coming directly from expo fonts. Make sure to import it right here. And then we can define all the different font families such as sans regular or different font types. It's going to be require such as dot slash assets slash fonts slash plus jakarta sans regular. ttf. And you want to fill it in for all of the different variants such as bold, medium, semi-bold, extra bold, and light. You should have six in total. After that, we can run a use effect hook which is going to be coming directly from React. And here we basically want to check whether the fonts have loaded. So I'll check if fonts loaded. If that is the case, we will simply hide the splash screen by saying splash screen coming from expo router dot hide async. And then we can re-trigger this whenever the fonts loaded variable changes. Perfect. Also, if fonts for whatever reason don't load like at all, we're going to simply return null. We cannot show our application without it. So, what's happening here? We're using the use fonts hook from expo font. You pass to it an object where keys are the names of the fonts you want to use and the values are actual font files. It returns a boolean variable that tells you whether the fonts have finished loading or not. The use effect watches for that boolean and once fonts are loaded, it calls the splash screen. hide async which simply dismisses the splash screen to reveal the app. But until then, this little check prevents the app from loading. And now step three is to sync this with Tailwind because that's how we're going to actually apply these fonts to our classes. So head back over to our global. css CSS and right here where we have our theme different colors and spacings within that theme maybe below the colors we also want to add the part for the fonts. So you want to do something like this d- font- sans you want to make that to sance regular and then you basically want to attach each one of these fonts to a specific class name. Those CSS variables map to the exact names we used in use fonts. So when you write a class name like class name font sas bold like this in your JSX native wind will automatically resolve it to sansbold family which points to the plus jakarta sans bold file. So let's go ahead and test it. We can head back over to tabs index. tsx and where we have this main welcome to native wind text. Let's change it to some other color. Maybe text- primary. We can make it a bit larger like text-7 XL. For this one, I'll keep the font bold and I think by default it's going to be dark. There we go. And I'll simply make it say home like this is our homepage. Now, this is it with using just a regular bold font. But now I'll duplicate it and the one on top will use the font- sans- extra bold variant which is our custom font. The difference is present. The bottom one uses the system default bold font, but the difference in character, weight rendering, and letter spacing should be obvious. That's the difference between a prototype and a product. Now, we can also replace it within these three links. So, I'll simply add it after this margin top of four. And I'll add a font dash sans-bold. And you can immediately see the difference. I'll also remove these two links as we no longer need them for testing. And with that, our homepage is already looking so much better, but I will make this home just a bit smaller. With that in mind, typography is done. And from here on, every screen you build will use the same font vocabulary. Font sands regular for the text body, font sands bold for headings, and font sands extra bold for emphasis. It'll keep the entire app visually cohesive without you having to think about font names on every component. Even though this was a small feature, let's go ahead and commit it by running get add dot get commit-m implement fonts and then get push so that later on once we implement more features we can open up a PR and test it all with code rabbit. Now, our app is immediately looking so much better, but it'll look the best in the next lesson as we are about to dive into implementing the homepage.
Home UI
This is where the app starts looking real. The home screen has three distinct sections. a header with the user info, a balance card with the next renewal date, a horizontal list with upcoming subscriptions, and a vertical list of all subscriptions with expandable detail cards. We'll build it section by section, create reusable components as we go, and solve one of the most common React Native layout problems along the way. And take a look at this. Almost every single React Native application follows a similar structure. We have some kind of a header. We have a big call out and a card. And then we have lists, horizontal lists or cards, vertical lists or cards. And this is tricky to get done properly in React Native. So I'll teach you how to get it done well and correctly in this lesson. If you want to be a bit more precise, this is what we're developing. I also see that we're rendering some dates right here and dates here and here, which means that we're going to need some kind of a lightweight date library. So, I'll head over into the terminal and run mpm install dayjs. Perfect. We'll use it to format renewal dates and subscription timestamps. First, let's set up a couple of files we'll need. You can grab all of these from the GitHub repo in the description. The first thing you'll need is going to be the updated constants data file. Instead of just having the tabs which we're mapping over, we want to have the data for the homepage that we'll map over. Same practices apply. So in the video kit link down in the description, you can copy it and you can paste it here. It seems like a lot of stuff, but basically these are just some objects, fake dummy objects for now that we want to show on our homepage. Home subscriptions. We also have upcoming subscriptions, home balance, and then home user as well as the tabs at the top. Think of these as different data pieces that we want to display on our homepage. Now, you also have to ensure that within your type. d. ts, you have all of these different classes. So, you can also get that from the video kit link down in the description, which is going to include how an app tab should look like or how tab icon props or what a subscription should have. It should have an ID, an icon, a name, an optional plan, category, and payment method, status, price, and so on. So, this way, it's not going to complain. And then head over into constants and create a new file called images. ts. Here we want to centralize image imports. Same pattern as with the icons. You can say import splash pattern coming from at/assets/ images/splash pattern. You saw the image right here. That's the one coming from the design. We'll use it for the splash screen. And I also want to import the avatar coming from at slashassets slashim images/avvatar. pbng. And then we can simply export those two by default like this. Export default and then splash screen and the avatar. Perfect. Now alongside this, we'll also need a currency formatting function. Take a look at the design. We are working with dollar amounts. These are functions that you would oftentime find from a library or maybe in previous times go to Stack Overflow to see how other people implemented it. Nowadays, it's very easy to just ask AI to do it or if you want to have some fun and have some extra time, you can try implementing it on your own. But yeah, basically within a new folder that we'll create that's going to be called lib and then utils, you want to create that currency formatter function. I'll let Juny do all of that for me by opening up Juny and telling it to create a currency formatting function in lib/youutils. ts that takes in a value and currency as params and formats a number as standard US money dollar sign with exactly two decimal places defaulting to USD. You can use a try catch and handle the fallback. I use a program called whisper flow to narrate this to AI. I find it simpler and faster than to type. And now I will simply press enter and see whether Juny can create that new folder, a new file for us. Let's see lib is here. Now within the lib it needs to create a utils file and then within the utils file it needs to implement the function. Looks like it already did it. Perfect. Super simple. Basically what's happening here inl number format is a built-in JavaScript function for locailaware number formatting. It handles currency symbols, decimal separators and so on. And the try catch is there because if someone passes an invalid currency code, it would throw an error. We'll add more utility functions to this file later on, but this is good enough to start with. Now let's build the homepage from top to bottom. Open up the app tabs index. t tsx and let's start with a header showing the user's name and avatar and then the balance card showing total monthly spent. I'll delete everything we have within here right now including this text and we're just starting with safe area view with a flex one bg background and p5 within it. I'll create a view and that view will have a class name set to home- header. Of course, don't forget to import the actual view coming from React Native and make sure that we have this class name within our global. css. So, you can head over there and alongside everything we have, you can also override it with the final global. css file coming from the video kit link down in the description. You'll see that I added many other classes that we can use. And essentially those classes are applying some additional Tailwind classes that are going to make it easier and cleaner for us to style different components. For example, in this case we used a header which is going to apply a margin bottom of 2. 5 flex row item center and justify between. So now if we use it type something within it and maybe we can render a piece of text and type home within it. You can see home right here. I know I provided a couple of files to you in this video, but those are mostly just to speed up our workflow and to focus on what really matters, which is learning React Native. So yeah, within this view, let's create another view. And this one will have a class name of home- user. And within it, we can render an image coming from React Native, not Expo image. Don't make the same mistake as we did before. And then give it a source set to images which you have to import avatar and a class name set to home- avatar. So you have my profile photo right here. It does look a bit blurry here on my 4K screen, but when I look at it on my phone from far away, it actually looks good. Still, we want to be able to serve images that are a bit more higher quality than just this. So, by the time you're watching this, I will have modified the assets so you have properly sized images, icons, and everything else. For now, since this one is blurry on my end, I'll get the one that I have on Twitter and save it as avatar and render it right here. There we go. That's better. Now, below the image, let's render a piece of text that's going to have a class name set to home user name like this. And we want it to dynamically render the name coming from our constants. Later on, that name is going to be dynamic, but for now, it's going to be static coming from home user data. At least we're preparing it for the time when it's going to be dynamic. So, under home user that's stored under data, you can see that it's set to Evelyn Parker. I'll do Adrien JS Mastery just to make it a bit more well dynamic or personalized for now. Then below this view, we can render another image. And this one will have a source of icons add. It's going to have a class name of home add icon like this. and make sure to properly import the icons coming from icons. ts. There we go. So now we can go ahead and add a new subscription very easily. At least we will be able to when the app is done. Now let's head below this view and create another view below this top header part that's going to have a class name set to home balance card. within it a new piece of text that'll have a class name set to home balance label and it'll render the balance. Below that text, a view that'll have a class name set to home balance row. Within it, another piece of text that'll have a class name set to home balance amount. And within it we can render the amount by simply getting it from home balance amount. If you render it like this, you can see that it's not formatted. But if you wrap it with the format currency utility function we created like this and you make sure to import it from the right place and then reload the application, you'll see that we have a syntax error. Okay, that's good. I thought something else was broken. Oh no, I just have a double parenthesy right here. If you fix it, now it's going to be formatted like an actual number. And below it, we can render another piece of text that has a class name set to home balance date. And here we can render a date by saying home balance dot next renewal date which is going to look like this not formatted but you can call the dayjs library pass it as the first parameter and then call the format method on it and format it as mm/ dd which is month and day like this and that gives us our balance. balance card. So, as you know, our home user and home balance are coming from our constants file. Right now, this is just hard-coded data. Later, when we connect it to the backend, we'll replace these constants with real API responses. But building with static data first lets us focus purely on layout and components without worrying about loading states or network calls. And then obviously these class names like home balance card or home header are coming directly from our global. css. If you at any point want to know which exact classes or styles are being applied on each one of these class names, you can just command or control-click them or search them across the codebase and you should be able to find them within the globals file. But next, let's focus on the upcoming renewals section. This is the one that I'm talking about. It's not as easy as it looks because we are dealing with a horizontal scrolling list that shows all subscriptions that are about to renew soon. For that, we'll create two reusable components which we will reuse later on. The first one is going to be the list heading like this one right here alongside with the button. You can see how it's being reused multiple times. And then the second one is going to be this upcoming subscription card which we'll also be reusing in a couple of places. So let's develop those two components first by creating a new components folder if we don't have it already which I don't think we do. So a new folder called components and within components a new file called list heading. tsx. run RNFE to quickly spin it up and we can implement it. Now before we display this one on the homepage, let's also develop the second one which is going to be under components and simply create a file called upcoming subscription card just like this and also run RNFE right here. Now we can import both of these within our tabs index. tsx TSX so that we can at least check them out and see how they look like. So inside the safe area view but after the balance card that is this one right here we want to display another view. And this view will render a list heading like this which you must remember to import at the top from components list heading and pass in a title of upcoming. For now, it'll just say list heading. And later on, as you know, we will have another section which is going to be all subscriptions. So, we might as well immediately render another view below which is going to say title all subscriptions just to show you how we can make it dynamic. And now we can head into this list heading, take in a prop called title, and just display that title right here. So now it says upcoming and all subscriptions. And we can style it a bit further by giving this view a class name set to list dash head. Below it a piece of text that has a class name of list-ashtitle and it renders the title. Then below that text render a touchable opacity. basically a fancy name for just a button that has a class name of list dashaction and within it a piece of text that has a class name set to list action text and it can simply say view all and that's going to display this great looking button. I'm also noticing that our prop is complaining that we are not specifying any type which it should have. So you can simply define a type of title is set to string. But in this case I think I already provided the interfaces or types for all props. So you simply have to say list heading props and it'll automatically recognize that this is a string. This is coming from this type. d. ts file where we define how a upcoming subscription should look like the subscription the tab icon and so on. just so we know that we're type safe. Great. So now we have a very simple section header with a title and the view all button which we'll reuse for both of these two sections. But now let's create this actual subscription card. I'll display one of these cards below the header. That's going to be the upcoming subscription card, a self-closing component. And now we can get it developed. But this card has to accept some data into it, right? It cannot know what it needs to show by itself. So to this card as data you can pass the upcoming subscriptions zero which is going to be the data for the first one like Spotify subscription right here. And then if you head over here you can get access to the data prop by dstructuring it. And then out of the data prop you can also get access to the name, the price, the days left as well as the icon. And then you'll be able to display them right here. And these are going to be of a type upcoming subscription like this. Later on they're going to come without this data object, but for now we can leave it as it is. So let's show how it should look like. It's going to be a view with a class name of upcoming card like this. Then within this view, we'll display another view that has a class name set to upcoming dash row. And within it, we'll render an image coming from React Native with a source pointing to the icon that we passed into it and a class name of upcoming icon. So that's how this is going to look like. Then we'll also need to have a view with a piece of text that has a class name of upcoming price. And here we can display how much we're going to get charged. We're going to display the price by formatting the currency. So we're going to pass the price in first coming from props and then the currency itself. Of course, we also have to dstructure the currency from the props. Perfect. 599. Below it, we can render another piece of text that'll have a class name set to upcoming meta, which is going to be how many days are left. Make sure that this doesn't take more than a single line. And then we'll say days left, and then we can say days left. So in this case, it's going to be 2 days left. And I can see that my webtorm automatically suggested a little fix and that is if days left is greater than one then say x days left else just say last day because you can't say 1 days left. Right? So this is that little um language fix right here. Perfect. Now we can head below all of these views and render a last piece of text with a class name of upcoming name with a number of lines set to one so it doesn't jump out and we can render the actual name. Pretty cool. This is our card. But now the main question is how are we going to display multiple cards like this one? A regular React way would be to just duplicate it a few times and change the data and that's it. You have the cards, but that's not how we do it in React Native and Expo. Here it's much simpler and to be honest, I prefer it over just mapping over the data. The way it works here is you create something known as a flat list. This is a list component from React Native that accepts data. So you can automatically pass a data object to it. In this case, it's going to be upcoming subscriptions. And then you can also pass how that item should be rendered. So let me actually do this on a new line. We have the data and then we have the render item. The item that will render, I think you already know it. We're going to get the item details and we're going to render it as the upcoming subscription card where we're going to simply spread all the properties of the item. That way, we don't even have to have this data, but rather we're automatically destructuring all of these properties. Perfect. So, that's it. That's how you do it. And you can see immediately we now have three of these cards and they're actually within a scrollable window. Pretty cool, right? But whenever you have multiple items, you also want to have a specific key. That's why there's a key extractor where you can choose which property from each specific data point is going to act as a unique identifier. And then check this out. To make it horizontal, you just have to pass it the horizontal prop. That is it, believe it or not. And currently there's a horizontal indicator always showing or at least when you swipe through it, it is showing. But you can also set that to false. So now you can swipe through the cards manually. And we'll let our UI do the speaking. See, I purposefully made this card such a width so that you can barely see the start of the new one. So the user knows that they can swipe more. Pretty cool, right? This flat list also has a special component of list empty component which figures out what do we show if there's well nothing to show. In this case, we can just show a piece of text with a class name of home empty state that says no upcoming renewals yet. Pretty cool, right? This single prop on the flat list horizontal switches the entire flat list from vertical scrolling to horizontal scrolling. The cards will render side by side and you can swipe through them. And if for some reason nothing rendered like if it was an empty array, you can see an immediate no upcoming renewals yet which is coming from this list empty component. And that's what I love about React Native. This flat list has so many different things that you can pass into it. Well, not this many, but it does have a lot of different things that you can do such as item separator component, list header component, list header, component style, sticky header. I can just type different letters and see so many different components that you can pass to it that are going to modify how this list looks like and behaves. Pretty amazing stuff. But now, let's focus on the main subscription list. This is where things get a bit more interesting. Each subscription card is tapable and tapping it expands the card to show details like payment method, category, start date, renewal date, and status. And then tapping it again collapses it. First, head over to utils. That's going to be right here under lib. And make sure that you have a utility function for formatting the status label and the subscription date and time. You can get the final utils. ts ideas in the video kit link down in the description. Now, let's create a new component under the components folder, which is going to be called a subscription card. tsx. Within it, run RNF. And let's simply import it within the homepage so that we can see something. I'll add it right here below our all subscriptions. And there we go. There's a subscription card. Let's start with the simplest possible version of this card. just the name and the price, nothing else. We want to see it on screen immediately. So, I'll assume we'll pass in some props like the name, the price, the currency, the icon, and the billing. And then these are going to be of a type subscription card props. Within here, we'll have a view that can have a class name of subcard, as in subscription card, and bg card. So we can render the background as well and you can already see it on the screen. Now we can add another view right within it. This view will have a class name of sub head and within it another view with a class name set to sub main. Within it I'll render an image coming from React Native with a source of the icon that we want to display and a class name of sub icon. So, this is a subscription icon, but we can't see it yet because we're not passing in those props yet. Then below it, we'll render a view with a class name of subcopy. And then within that view, a text that's going to have a number of lines set to one and a class name set to subtitle. And then we can simply render the name coming from props. Finally, below this view and another view, but still within subhead, add a view that's going to have a class name of sub price box. And within it, a piece of text that says sub price displaying the price, and then sub billing, which is going to display the billing. So, we can dstructure it right here. So we can render it right here. Now for the price, don't forget that we'll have to format it. So use the format currency utility function which we created earlier. And this one will accept the price as well as the currency we want to show it within. So with that in mind, we can head back over to our homepage and pass some of the props such as daome subscriptions zero. This is going to pass in the data for the first home subscription. In this case, for Adobe Creative Cloud. If you save it and go into the subscription card, you'll be able to see Adobe Creative Cloud and how much we're paying each month or year. That's our card icon on the left, name, price on the right, and billing period underneath. That layout works. The theme colors apply and the text will be truncated in case it is too long. Perfect. Now, let's layer on additional features. Each subscription card will have its own color. If it was just like this, that would be a bit boring. And when the card is collapsed, it should use that color as its background. We'll use clsx to conditionally switch between the custom color and the default style. The style prop will handle the actual color value since it's dynamic data and not a tailwind class. So right here at the top you can give it a style property and check whether the color exists. Uh the color of course is going to be coming from the props. So we can dstructure it right here. If a color exists then we'll render the background color and simply set it to color. that on its own won't give it the color yet. Instead, we have to set the class name to be clsx. Always give it a subcard and a bg card like this. And then we'll apply the style through the background color. Of course, if we don't have the color, we can just render undefined in this case. Now, if you reload the application, you can see that it gets applied. Looking good. Now, let's add this subtitle line below the name. This will show the category, the plan name, and renewal date, whichever is available first. This fallback chain means the card always has something useful under the name. So, I'll render a text and give it a number of lines set to one to truncate it automatically. Set the ellipses mode to tail and then give it a class name set to sub meta as in metadata. We'll first check whether we have access to the category. If so, we'll render it and trim it. If we don't have access to it, we'll check whether we have access to the plan and trim it. Else, we'll check whether we have access to a renewal date. And in that case, we'll format the subscription date and time with that renewal date in mind or render an empty string. Of course, all of these are props coming from the top. That's going to be category plan and renewal date. So now we can see that it says just design which is the category of the subscription. And finally let's make it interactive. We want to switch the outer view with a pressable component. This one here. So I'll say pressable and it'll automatically import it from React Native. Pressible is a newer API that React Native recommends over touchable opacity. It gives you more control over press states and you also get access to the expanded and on press props which are going to be super useful. And we also want to use CLSX to switch between different states whether it's expanded or collapsed. When expanded, the custom color needs to go away. So we use the neutral expanded style so the text detail stays readable. We can do it like this. For the on press, I'll simply pass the on press coming from the props. So, I'll get it here. And this expanded state actually doesn't belong to pressible, but rather we're going to pass it as a prop as we keep track of which ones did we click. And based off of it, we can make some changes such as we can always give it a subcard class name, but only give it expanded class name if it's expanded. So that's going to be if expanded display subcard expanded else display bgard. Same thing for the color. We'll only display it if it's not currently expanded. There we go. Now, to be able to test it, we have to wire it up to a tap. So, in the home screen, that's going to be back here. Let's add a state and pass it to the card. Right here at the top, I'll say const expanded subscription ID set expanded subscription ID. And that's going to be a use state coming from React of a type string or null at the start. Then right here within this static card, we can also pass some additional information such as the expanded state, it'll be expanded if the expanded subscription ID is equal to the ID on this card. And we'll also give it the on press, which is simply going to call the set expanded subscription ID state and set it to the one that's currently set. But we also have to clean it properly in case it's already expanded. So what I'll do is I will check for the current ID and then if the current ID is equal to this one, we'll set it back to null. Else we will set it to the real expanded one. Now if you tap on the card, you'll see a background color switch between the custom color and the expanded style. Nothing shows in the expanded area yet because we haven't built it, but the tap interaction is confirmed working. You can see the state toggling in real time. Now, let's add what shows when expanded. After this sub head view, we can collapse it. And below it, we can add another part that's only going to be true if it's expanded. So expanded. If that is the case, then we display the subbody and a text. And you can see that now the text appears as we click on it. Pretty cool. Now, instead of simply saying subscription details go here, let's render another view that'll have a class name set to subdetails. And another view right below it that'll have a class name set to sub row. And between that, another view that'll have a class name of sub row copy. And finally within it a piece of text that has a class name of sub label and it says payment. And then we can render another piece of text right here with a class name of sub value a number of lines of one so it doesn't jump out and an ellipsus mode set to tail so it truncates from the end. And here we can render the payment method question mark. trim. This payment method of course coming from the props as well. Now if you save it, you can see payment with Visa ending in 8530. So you know which card you use to pay for this payment. Now we can duplicate this sub row a couple of times. You could have mapped over it and you totally can, but for now I will just duplicate it a few times. one, two, three, and four. Here, instead of payment, we will say category to know what is the category of this payment. And we'll change it to category. Or if we don't have access to category, we can display the plan that we chose to go for. So now you should be able to see right at the bottom. Oh, we cannot scroll yet, right? That's another thing that we'll have to fix, which is pretty huge and an important takeaway. But yeah, for now we can just display these other pieces of data. We have payment. Then after that we have category which I will bring right here to the top directly below payment. Then after the category we have another one which is going to be started. So when did this start and that's going to be set to if we have access to the start date. So start date. Then we want to format subscription start date time by passing the start date or else we display just an empty string. And this start date of course is coming directly from props as all of the other values are too. Again we'll be able to see it once we fix the scroll. But let's let that be the pain for now. So it's going to be that much better when we fix it. After this started, we want to show what is the renewal date. And once again, we'll simply check whether we have access to the renewal date. And then if we do, we'll format the subscription date time and pass in the renewal date or an empty string. And finally, we want to be able to figure out the status of this subscription. Is it active? Is it not? What is the status? And the status if it exists is going to look like this. Status format status label and we'll pass in the status or just an empty string. Perfect. So now we are rendering all the details. As soon as you click on it, the details are there. The payment method, the category, the status, and then it collapses once it is done. So now that we've got one card working perfectly, it's time to turn it into a list. Specifically, we're going to use the flat list one more time. So simply render a flat list and pass in the data. In this case, it's going to be home subscriptions. And for the render item, we obviously want to render the subscription card. So let's delete this one. And we'll pass the right one properly directly to this flat list. We first have the data. We then have a key extractor which is going to be item ID. And then we have the render item where we get access to each item and then render a subscription card. Each one of these subscription cards is going to get all the different props. So we're going to spread the props. We're going to also pass the expanded state which is going to be equal to expanded subscription ID is triple equal to item ID. If that is the case, well, that means that it is expanded. And we also need to have an on press where we're going to set its active state. So, if it's currently clicked, uncclick it. Or if it's not clicked, well, set it to the expanded state. And immediately, you'll now be able to scroll through these subscriptions. Take a look at that. Pretty cool, right? But only in the bottom part of the screen. What we can also do is provide the extra data and that's going to be the expanded subscription ID. We also want to add the item separator component which is just going to be a simple view with an H4. So it's going to create some spacing in between the cards. Then we want to use the shows vertical indicator and set it to false as we know that these are scrollable. And finally, we want to render a list empty component, which is going to render the home empty state that's going to say no subscriptions yet. So now you get a full list of subscription cards, each with its own color. If you tap any, it'll expand. If you tap it again, it'll collapse. And opening one automatically collapses the other. But even though it seems to be working well, we have one huge problem right here. We're scrolling within this tiny little container and we have this horizontal scroll here and the balance at the top. Overall, it isn't the best experience. The subscriptions list scrolls just fine, but the header and balance card above it are stuck. The whole screen doesn't scroll as one piece. So, remember the crash course. Nothing scrolls by default on mobile. The flat list handles its own content, but everything above it isn't part of that scroll. And you can't just wrap everything into a scroll view because nesting a scroll view inside a flat list with the same scroll direction creates performance issues and React Native will give you a warning. So, the fix is clean. You have to move all the content above the flat list into the flat list header component. Yep, that's another one of those very handy props that you can pass into the flat list. It's list header component. Basically, what renders above the list? In this case, we will take everything within the homepage. I mean, all the other views that we have right here, starting all the way from the home header, including the home balance card, and a view for the upcoming, and we're going to end there. So these three things we want to take and we want to add them as the list header component to this flat list. But let's first wrap them with an empty React fragment so they can actually work together like this. And now paste what we just copied. Take a look at this. The all subscriptions now shows at the top and view all. But now the whole thing is actually scrollable. Already much better. But there's still some modifications we can make to it such as removing this view which we no longer need. That's wrapping everything. Also taking this list heading for all subscriptions and moving it right here below where we actually render the data. We first have the home header. Then we have the home balance card. Then we have another view to which we can give a class name set to margin bottom of five to divide it a bit from the next list. And then after that last view, we can display a list heading for all subscriptions. And now the entire screen is one unified scrollable list. The header, balance card, and upcoming section scrolls away naturally as you go through the subscriptions. We didn't rewrite any components. We just moved them. And the horizontal flat list for upcoming subscriptions still works fine nested inside of it because it scrolls in a different direction. Vertical parent horizontal child means no conflict. And notice how currently the cards are being hidden by our floating tab bar. We can fix that with a another prop called content container class name and give it a padding bottom of 30. So test out the full screen. Scroll through everything. Tap different cards and verify it works. The entire page now scrolls as one unified experience. This lesson was a big one. We implemented multiple components, utility functions, and a scrolling pattern that's so easy to get wrong, but now you know how to do it properly. simply take everything that needs to be within it that also needs to scroll and add it to that one flat list. So with that, let's go ahead and commit and push so we can verify with code rabbit that we didn't make any huge mistakes. I'll open up the terminal and clear it and run git add dot get commit-m implement the homepage and run git push. This will push it over to the dev branch. So when you go back to it, you'll see that it had recent pushes. So you can just open up APR. There aren't any conflicts. But let's first wait for Code Rabbit to provide its review so we can do a proper check whether it hopped through diff and found carrots or some landmines. I love these short messages. But yeah, let's give it a minute and see what it finds. This was a huge PR. We introduced the subscription management home screen to the app. The main point for everything. Changes include custom font loading with expo configuration, new React native components for displaying subscriptions including subscription card, upcoming subscription list heading, TypeScript type definitions, data models, utility functions, dummy data, and Tailwind styling. The home screen now renders a datadriven flatlistbased UI replacing previous static navigation content. Wonderful. Here you can see what happened within each one of these new components. And I love this. I think this is new. Code rabbit now shows possibly related issues directly on GitHub. So if fontquicks send bold is not working on a YouTube video. So apparently somebody complained that for them the fonts weren't loading and then if this was a real issue most likely the maintainers would comment here and we would be able to see how to fix it. That is absolutely amazing. Thankfully we don't have any issues with the fonts because our fonts load with grace and subscriptions now shine. Cards expand and collapse on command. So fine. So finally we get some nitpicky comments and then actual review. We have a major issue right here. Missing splash screen prevent autohide async call without calling at module load time. The splash screen may autohide before fonts finish loading causing a visual flash. The standard expo pattern requires preventing autohide first. This is actually a huge issue and I'm super glad that Code Rabbit suggested it. The only thing we have to do is add this line which I'm going to be honest I totally missed. So that's going to be within app layout. tsx and right at the top we just have to say splash screen prevent autohide async. Perfect. So this one is fixed. We can go to the next one. Fix section title grammar for userfacing copy. It says all subscription, but should be all subscriptions. Okay, that's a little plural issue right here. So, I'm going to search for all subscription and modify it to all subscriptions. I mean, it matters, right? We got to make sure that the app feels and actually is polished. Then, we have another minor issue. The touchable opacity is rendered without an on press prop. Specifically, it's talking about a view all button, but I know we're going to add that on press later on once we implement the routing functionality for the page where we can display all the subscriptions. Finally, we used some blank detail values in expanded rows. Um, where is that going to be? Lines 34 right here. Payment method. trim. Okay. can render empty content when optional fields are absent, which leaves unlabeled looking gaps. Yep, this is not good. So instead, we should use a consistent fallback like not provided or conditionally hide the empty rows. I agree with that entirely. So if it doesn't exist, we can render a fallback, which is then going to say not provided. Yep. A much safer way to do this since I don't feel like modifying all of these lines line by line. In this case, we can test out the functionality to prompt an AI agent to fix this for us. So, simply copy it. Head back over and open up Juny. I'll open up a new chat and simply paste what I copied. This part right here is happening in the subscription cards, I think. So, if I head over to subscription card, we should be able to see how Juny is going to oneshot it hopefully by adding an additional fallback and then mentioning those fallbacks right here. It looks like it's still working on the changes and it's almost done. There we go. It simply says not provided at the end if this doesn't exist. Much safer. We have another major issue and that is that the home balance next renewal date is currently a past date. Yep, that's true. But that's totally okay because this is just fake dummy data. We can easily fix it once the real data comes in. That's going to be fixed. Oh, another typo um here in the images. ts constants. We said splath pattern instead of splash pattern. So I'll head over to constants and then images. Okay. And then simply fix this over to splash pattern and see whether we use it somewhere as splash pattern. No, we haven't used this yet. So, we're good. You see how good it is to have code rabbit. I mean, the more you develop with AI, the more AI could make some changes. But just having this sanity check to go through it and to see what you coded and how your project code grew is super useful for making sure that it scales whether you code it or you code it with AI agents. And finally, the last one is a minor issue. Uh, in this case, the fallback ignores the currency parameter. So to fix it, we just need to return value to fixed like this. That's going to be within our utils right here. Value to fix like this. Perfect. That's it. So now that I implemented all the changes, we can go ahead and commit it by saying get add dot get commit-m implement code rabbit suggested fixes and run git push. The changes will automatically get recognized and we can merge another PR now including our full homepage. This has been an absolutely amazing lesson and I can't wait to dive into the next one.
Clerk Setup
Up to this point, we've built routes, tabs, style components, and a complete home screen UI. Everything so far has been clientside layout with some hard-coded data. But now we're adding the first real production layer, and that is authentication. For this project, we're using Clerk. If you watched any of my previous courses, you've seen Clerk handle O in web apps. But thankfully, the same platform works for mobile, too. I mean, just think about what authentication actually involves. You need sign up and sign in, email verification, session management across app restarts, secure token storage on the device, social login providers, password reset flows, device trust, and additional verification when something looks suspicious. Building all of that from scratch is weeks of work, and getting that security right is the hard part. Thankfully, Clerk handles all of it. You integrate their SDK, configure what you want in the dashboard, and write maybe 20 lines of code. Your time then goes toward the product experience, not just working on the off. And the best part is that it's free. Yep. And they mean that in the full sense of its word. The free plan gives you unlimited applications and you get up to 50,000 monthly active users per app. So whether you're building a side project, a vibe coded app, or an early stage startup, you're not going to hit that limit for a long time with no credit card required. So let's set it up together. Click the clerk link down in the description and create a new account if you don't have one already. Then head over to the dashboard. Then create a new application. Let's call it JSM Recurly. and you can select email plus any other out of the hundreds of different providers that you can sign in with and then create an app. After creating the app, you'll land on the overview page and by default it's going to show the instructions for Nex. js, but you can also switch to Expo giving us the full setup. We already created the app and now there's some other steps that we can follow. But the only thing I want you to do for now is just simply get your Expo public clerk publishable key by copying it, heading over within your app. And this is going to be our first environment variable that we're going to add. So I'll create a newv file and add this expo clerk publishable key. You don't need to follow the rest of the setup on this page as I'll teach you how to handle the SDK integration step by step. And before we leave the dashboard, you can head over to the configure tab. And under email, you'll see that signup requires both a password and an email verification to log in. By default, this means that when a user signs up, they'll get a verification code sent to their email. This is already configured for us. We don't have to build separate email sending or verification logic as clerk handles the whole flow. You can also add other o methods right here under SSO connections. And yeah, as I showed you before, there's tons of different options that you can choose from. Oh, and one thing worth noting is that Clerk recently launched native components for Expo. If you scroll down on their documentation, you'll see native components. They're currently in beta, but essentially what this does is these are pre-built authentication screens that render using Swift UI on iOS and Jetack Compose on Android. So, if we check one of these out, they're going to look native on your phone because they are native. We're not going to be using them in this build as they require dev builds instead of Expo Go, and I wanted to keep the setup as frictionless as possible so that you can follow along nicely. But if you're already working with dev builds in your own projects, they're worth checking out. The docs are at clerk. com under their Expo SDK section. And later on once we deploy these projects to app stores and to their respective app stores and the play store we'll have to run a development build anyway. So that's a great moment to switch to using these native clerk components. Okay, that's the clerk setup account created application configured and API key is in your envir SDK into our export app and build the actual signin and signup screens.
Authentication
In the last lesson, we set up our clerk account, created the application, and grabbed our API key. Now, we need to actually integrate it into the app, install the SDK, set up the providers, build sign in and signup screens, handle session state, and redirect users based on whether they're authenticated. And we're going to do this entire implementation with AI on purpose. This is not because we're skipping the learning. It's because clerk setup is exactly the kind of work where AI excels when the inputs are clear. Think about what we're about to do. Install packages, wrap the app with a provider, create two forms of validation, call specific SDK methods, and handle navigation based on off state. Every single one of these steps is documented in Clerk's official quick start guide. It's procedural and well doumented. It's the same pattern whether you're building a subscription tracker or a social media app. And that's the definition of work that AI should handle for you. Because the real skill isn't typing out forms. The skill is knowing what to ask for, giving AI the right references, and then reviewing the output like an engineer. You understand the problem. You know how the solution should look like. You hand AI to docs and you verify the results. That's what I call agentic development. And companies are actively looking for developers who work this way. If you're spending 30 minutes manually wiring up something that AI does in 5 minutes with the same quality, well, you're not being more thorough, you're being slower. And Clerk actually makes this workflow super simple. If you visit the Expo Quickstar docs, you'll notice they have this copy as markdown and direct handoff buttons for chat GPT cla cursor and other AI tools. The docs are written to be consumed by AI, which means the output quality will be high if you use the docs as the source of truth. So, here's what we're going to do. Go to the expo quick start, click copy as markdown, and then head back within your IDE. Open up Juny or your coding agent of choice and start a new chat so it has clean context. In this case, I'll switch over to Claude to show you how JetBrains's AI chat can handle any kind of an agent. And I will simply paste everything we copied over from Clerk, the entire documentation document. Now, if you hover over it, it'll tell you that it is 2,000 tokens longer than allowed. So, I'll show you a trick. Right at the top, I'll provide you with three different messages. The first one is going to be the first part of the expo documentation. The second one is going to be the second part of the expo documentation. And the third one is going to be my prompt that you should take into account when implementing all of these features. After every single message, ask me to provide the next one. So, this is a cool little trick that allows you to divide the documentation into multiple prompts if you're blocked with the total limit. So, now that allows us to go a bit down to about half the content and then from here on we can copy all of that until the end. Remove it from here. Turn on the plan mode. Press enter. It's going to ask us whether you want to install cloud code in AI assistant. It'll reply with, "I've read the first part of the documentation. Please provide the next part. " I'll paste the next part. And finally, we can provide the final prompt. It's going to look something like this. I'll share it with you in the video kit link down in the description so you don't have to manually type it out. Study the whole codebase and attached clerk documentation to implement a complete custom clerk oddflow for this expo app with production grade signup and sign-in screens with logic validation and navigation. The UI must strictly match the existing app design system and native wind patterns. Use the attached design only as layout inspiration, but keep the O polished, brand native, and focused on conversion, clarity, and trust. And now we can also attach the design screenshot. It's always good to give it some kind of visual reference. For this, you can head over to the design, find the login screen right here, and then simply export it as JPEG, and then you can simply drag and drop it in and run it. But it looks like the claude agent within the Jet Brains's AI chat doesn't yet support images. So a quick workaround is to go to any kind of an image upload service like imageB on Google. Upload this image and then that's going to give you a URL that you can then pass over to Claude without having to manually upload it here. You can just provide the final link where you uploaded that image and then run it. Now since we are within the plan mode, it's going to take into account all of these messages, think about what we want to achieve and only then will it prepare a plan for us to start actually implementing it. This is what I like to call specd driven development where you create a detailed specification first, go through the plan, and only after dive into the actual code that's implementing the feature that you want to develop. So let's give it some time and then we can come back to check it out and go through all of these files line by line very quickly. We get back the full authentication plan with the analysis what this current app is and what we want to add. We want to update the root layout and wrap the entire app with the clerk provider. We want to create the O layout, build the signup screen, build a sign-in screen, then protect the home routes. update the home screen and then add some type definitions as well while following the design system. It lists out the key features, the key files to create and modify and we are ready to accept the plan. So let's give it some time to actually implement it. It'll now go through all of these seven to-dos one by one. I'm going to pause the video so we can check out the result as soon as it's done. And there we go. It says that it got it all implemented. It first went through the entire codebase to understand the existing patterns. It parsed through the clerk documentation and generated the full implementation. It even gave us a diff that we can now check out on the left side right here for all six different files. What I'll do instead is I'll keep all the changes so they get transferred to our codebase. And then whenever you do this many changes, it's always a great idea to restart your Expo server. So, I'll press command C or control C and then press MPX expo start to rerun the application. Press the letter R to reload the project. Or if the letter R doesn't work, you can just hold three fingers on your device like this and then just press reload there. Either way, take a look at that. We have the recurly subscriptions page. Sign in to continue managing your subscriptions. or if you're new, we can just create a new account. Wonderful. So now we can switch between those two pages and we can't see any other pages that we've developed so far until we actually subscribe. Now the next step is to take the phone in your hand and actually create your first account. So I'll go ahead and autofill it right here. Oh, the autofill was super quick. I'll actually enter a strong password three times. 1 2 3 and click create account. It's going to say creating an account. And take a look at this. This is also a benefit that you're getting from using clerk. They check whether your password has been found in an online databach. So they say please use a different password. And you can see how well our AI agent implemented validation and error handling because we get this message right here. So we know exactly what we have to fix. So I'll go with something a tiny bit more secure. I'll turn on these characters and just type the entire row. Oh, that has been found in a data breach, too. So, I'll do something random instead. I'll go with just a bunch of random letters and characters. This should be strong enough. So, click create an account. Perfect. And now we get to a second screen, which is a verification code screen. And no, this isn't just a fake dummy screen where you can type anything and get in. You'll actually get a real email in your inbox with the verification code. If you don't get it within 30 seconds, go ahead and check out your spam folder and move it into your inbox. And then once you get it, you can take your phone and type it out. Notice how our keyboard automatically shows numbers right here. 7 91 493. Verify email. and we are verified and we get navigated over to our homepage now with our profile photo coming from clerk and our email being displayed right here at the top. Now, if you head back over to your clerk's dashboard under users, you'll see that we have one user right here. That's the account you just created right there in the dashboard with all the info such as the email, the creation date, session info, all managed by clerk without us having to write a single line of user management code. I'll go ahead and upload the JSM logo right here and type adrianjs mastery to have a bit of a better UI within our app. So as soon as this gets updated, you can see the user updated got changed and within our application the changes are going to be instant. You just have to reload your app and take a look at this. Immediately we can see the new logo and the name change. Now let's open the generated code and understand what our Jet Brains AI chat with cloud code actually did. This is the important part because letting AI code write the code doesn't mean you skip understanding it. First, let's head over into the root layout. That's going to be right here under app and layout. You can see these green lines which indicate the new lines that have been added. We imported the clerk provider and the token cache. We got the publishable key from our environment variables and have a little check to see that it's actually there. Once that is done, the only thing that we're doing is wrapping the entire application with the CL provider and passing in the token cache. The token cache uses the Expo secure store under the hood which encrypts the session token on the device, which means when a user closes and reopens the app, they stay logged in without reauthenticating. And I can show you that by reloading the app right here. And we're in. Next, the navigation logic. AI also added the use O hook in a couple of places. We can see it within the tabs pages right here, specifically within the tabs layout. It's imported right here at the top and it's used right here to check whether the user is signed in and whether everything is loaded. If the user isn't signed in, then they're going to be redirected back to the sign-in page. Else, we'll let them take a look at the homepage. The key thing to notice here is where this check lives. It lives in the layout, not within the individual screens. That way, every screen inside of the tabs group is automatically protected. Then, let's take a closer look at the signin page. This is a longer one, and the entire thing was developed by AI for us. The sign-in screen follows a standard form pattern. email and password inputs, form validation, error handling, and a call to clerk's signin. As well as other sign-in methods such as signin, finalize, sign in, verify email code, and more. And notice that the UI here uses the native wind classes and theme colors, not the clerk's default styling. That's because we specified to match the existing design system in the prompt. The form handles loading states and error messages from clerk's API showing them in line and below the relevant inputs. You can see that right here where we have a specific input like an email address. Then there's a text input and then if the email has been touched but it's not valid, we can say please enter a valid email or if there are errors regarding the email, we show them right here. Signup is very similar but has one extra step and that is email verification. After the user submits their email and password, clerk sends a verification code. The screen then transitions to a code input view and once the user enters the code and it verifies successfully, they're signed in and redirected to the home screen. This entire verification flow is handled by clerk's API. We didn't build email sending, code generation, or verification logic. We just call the SDK methods in the right order. Now, what if I want to quickly log out so that we can check the odd page one more time because it's so good. Well, I'll open up Juny one more time and simply tell our AI agent to add a logout button on our settings screen so that I can retest the entire authentication flow once again and press enter. It'll do it within a couple of seconds. Then right here on our settings page, we'll have a log out button so that we can go through the entire flow one more time or rather this time we'll be testing the signin page because we already created our account. If I remembered the password that I entered that is there we go. This is amazing. So we can see the account ID when we joined it basically created the whole profile or settings page for us. So I'll keep those changes. I'll press the sign out button. We get redirected back to sign in. Enter my email and password and click sign in. This is perfect. I even get asked whether I want to save my password for this app to which I'll say no thank you. And with that, we're back to our amazing homepage where we can track our subscriptions, but this time with a real user account. So, here's what just happened. You already built a solid foundation. You understand React Native components, styling, routing, and layouts. You know how to code. What AI did here wasn't replace that knowledge. It used it. Our AI agent read your codebase, understood your patterns, and generated the code that follows them. So instead of 30 minutes of manual implementation, typing out forms, wiring up SDK methods, and handling edge cases, you spend 5 minutes writing a good prompt and reviewing the output. That's not cutting corners. That's knowing where your time is most valuable. Most tutorials either teach everything manually or go full vibe coding mode with no understanding. This course does both. You learn the concepts first and then you use AI to implement them efficiently. That combination and deep understanding plus intelligent execution is what makes you dangerous as a developer. And coincidentally, it's exactly what I'm building a deeper course on right now because I believe that's the future of development. I'll leave the link to the wait list for this course down in the description so you can become a part of it and tell me what you'd like to see before it's out so that I create it specifically for your needs. So with that in mind, our o code is now done. But here's the thing, why should you blindly trust this AI generated code? I mean, we walked through it. It looked good and it does work, but why stop there? Let's use code rabbit to review everything our AI agent just wrote. AI generated the code. Now different AI reviews it. So open up your terminal, run git add dot git commit-m feat clerk off implementation and then run git push. Then within your github repo, you'll see that this branch had the recent pushes. So you can simply compare them and create a pull request. This was a large one. 34 files changed. So, let's see what Code Rabbit has to say about it. Hopefully, it's not like this little snippet saying that it's a magic trick. Now, we can see it, but then it's undefined. Let's see how we can actually improve this code. Code Rabbit will now automatically review the new code on your Open PR. It'll check for security issues inside of the O flow, missing error handling, type problems, and anything that our AI agent might have missed. It's basically two AI systems checking each other's work, which forms a production workflow that you can actually trust. And the review is in. This PR integrates clerk authentication into expo react native app by adding the clerk provider at the root level. Implementing complete signin and signup flows with email verification and multiffactor authentication support and protecting the tabs interface with authentication gating. Pretty cool stuff. What I also love seeing within code rabbit's review are these sequence diagrams which tell you exactly what's happening. And this one is quite a long one. So let's go through it together so we can better understand what's happening. User opens the app and reaches the expo app at the root layout where we initialize the clerk provider with the publishable key right here. Then the O state loads and it checks whether we are authenticated or not. If we're not authenticated, it redirects us to the O signin page which we show to the user. The user then enters the email and password. We submit the credentials, call the sign in that password with email and password attached. And if we get to a password success, we return the session to the user. If multiffactor is turned on, we also need to send over the verification code. If they're already authenticated, we simply bypass the O, fetch the user profile, return user details, and render the profile data to the user. Amazing stuff right here. This was a pretty long PR to review. So, let's go through the review together. First, we have one major potential issue where the Expo clerk publishable key is shown at app layout. I mean, this key is public, but it's still better to keep it in an unttracked env. So, here's a prompt for an AI agent to fix it. I'll copy it, head over into Juni, and paste it right here within our chat. And this key is being used right here within the root layout. So let's send this message and let's see what our AI agent has to say about it. It says that it'll verify and fix the security issue by removing the exposed clerk publishable key from the repo. There we go. It created the enenv. ample where it says replace me and then it mentions that this env file mustn't be committed and it added it to the get ignore. Initially we should have used the env. local. So now I'll accept all the changes and I think it's going to remove it from GitHub for us as well as from the git history. Perfect. This is exactly what I wanted to see. The exposed test key in the previous commits should be regenerated in clerk dashboard for security. So in this case, code rabbit really caught something important and that is that we should always be careful with our keys. I'll head over to our dashboard under configure developers or rather we're going to go to settings and API keys. And here we have our publishable key which I'll copy. Head over into ourv. And then paste it over here. Currently it's the same one, but once we turn to the production version of our clerk instance, we'll replace it. So we're good to go. So this one is fixed and we can move to the next one. It says move clerk provider outside the font gate to enable parallel loading of fonts and o. This is very interesting. Line 32's font gate prevents clerk provider from mounting until the font loads. This serializes startup fonts load splash hides and then clerk provider initializes. Instead, if we move it before the font guard, the o can load in parallel with fonts. This is a huge upgrade, especially since the startup time of our application matters a lot. So, here's a prompt for AI agent to fix this. I'll open up Juny again and paste it into our AI agent for you. These code rabbit suggested fixes might be a bit different so you can go through your list. There we go. It says I can see the issue and it verified what code rabbit had to say and it automatically optimized it. Before clerk provider was inside the font gate. Clerk couldn't initialize until fonts loaded. Splash screen only waited for fonts and we risk of a blank frame between splash and first render. But now clerk provider wraps everything. O and fonts load in parallel. So we can check it out. Right here we use the use O. We have double the checks. And now everything loads within this new root layout content. I'll go ahead and accept all the changes. Perfect. This was an amazing find by Code Rabbit. See, even though our AI agent implemented this at the start, it didn't anticipate this. But then when Co Rabbit pointed it out, it actually implemented the fix. Next, we have a critical issue. Password valid is always true. Validation logic bug. The expression password length is zero or password length is greater than one covers all cases, making password valid. Hm. But is this really the case? Because we were able to see some issues with the password before. So again, I'll copy this to our prompt and paste it. But I'll ask it check whether this is actually a bug or not because at one point I was able to see a password related error. It's going to verify this potential bug. And it says you're absolutely right. This is a confirmed bug. Okay. Interesting. There we go. It fixed it right here. And now the validation works perfectly. If it's an empty password shows error when password touch. If it's a non- empty password, true. No error. So, let's keep the changes. Another critical issue fixed. We have this window href location will crash on React Native. Same issue as signup window is undefined in native environments. Oof, this is a big one. So, we need to add this check to see whether we're rendering this on the web. And only if we are, then we can use this window. Else, we're going to use linking. open URL. I'll also use our AI agent to implement this because it adds another layer and checks whether this is true. As you can notice, I like having AI agents check each other. Yep, and it's confirmed. This is a real bug. Window will crash on React Native. Same bug in signup. Let me fix both. This is amazing. I love it when I set up amazing AI agents. in this case code rabbit and clerk's mcps and our EI agent implementing all of these changes and when they work together so well and we can run these additional commands and keep all the changes there we go no more crashes on iOS and Android proper web behavior graceful fallbacks all three navigation points fixed consistently now we can push all the changes so I'll go to my terminal run git add dot git commit-m implement code rabbit suggested fixes and run get push with that all the changes will be recognized right here we might have some conflicts in the readme but that's totally fine because it's just the readme this is because our juni actually pushed some info on how we should properly store environment variables within here but I think it's mostly just about the title at least it appears so I will remove the changes Mark it as resolved, commit it, and we should be able to merge it. Wonderful.
PostHog
We've got authentication working. Users can sign up, sign in, and navigate the app. But right now, we have zero visibility into what they actually do once they're inside. Which screens do they visit most? Do they tap on the subscriptions card? How many users even complete the sign up but then never come back? Without analytics, you're just guessing. And Posthog will give us the answers. So, click the Post Hog link down in the description and let me show you how to turn this from just a project into a successful product with Post Hog. Click get started. Choose your region and register. For the name, I'll go with something like Adrian atjs Mastery. And then for the organization, you can choose JSM and then type your name right here. Then you can select your role. Most likely that's going to be in engineering. And if you have an extra second, you can type that you heard about this hog from JS Mastery. So let's go ahead and create a new organization. Then you'll be greeted with the onboarding page asking you what should we build first. So let's go ahead and pick the starting point. We can either track product analytics or web analytics, track session replays, use LLMs to analyze what's happening within our app, take a look at the feature flags to turn on or off specific features for specific user groups. And we can also run experiments, track errors, run surveys. There's a ton of stuff that we can do. For now, let's continue with product analytics. Here we have all the different technologies. So, we're going to proceed with React Native and we can follow the automated installation. We're going to use the AI setup wizard to automatically install and set up Posthog for our application. So, you can just copy this command mpx-y at posthog/wizard at latest. Then, within our application, open up the terminal and paste this command. Now, just before you run it, if you have clawed code or any other AI coding tool running in your terminal, log out of it first because the Post Hog wizard can sometimes conflict with active AI sessions and then we can log back in after the setup. So, press enter. It'll load for some time and then open up the wizard. This essentially is a CLI tool that reads your codebase and sets up Post Hog for your specific application. So instead of manually installing packages, creating providers and writing event calls, the wizard handles it. You can see that React Native had been detected. So click continue. It'll open up in the browser and you can choose your server and then it'll ask you for your organization and the project. So let's go ahead and authorize the Post Hog wizard. There we go. Login complete. Now we can return to our terminal and the wizard now authenticated with the post account will select your project then scan your entire codebase and it'll detect that we're using Expo and React Native and set everything up. Let's see how well it does as it's still analyzing the project. It just said that this process takes about 8 minutes. And this is super cool how it's teaching us about how we can continue using PostHog while it's actually analyzing the project and it'll set everything for us. What I'm going to do for now is I'm going to pause the recording and I'll be right back as soon as the project is analyzed. And on your end, you can also pause the video and go grab a coffee or something. Just as I paused the video, the right portion of the screen changed now giving us a bit more details on what the wizard is doing. So, it's first creating the post hog event plan, which I'm assuming will help us figure out what we actually want to track within the application. So, you don't have to worry about thinking which events you're going to trigger. Then, it'll install Posthog React Native and React Native SVG packages, create an app config, and then create the source config post file. Finally, it'll update the layout, insert the event tracking code into O, and wrap everything up. And at the bottom part, we can see what the Post Hook wizard is doing in real time, such as editing the layout file. I very much like this CLI interface. And honestly, I've never had this amazing of an experience where I don't really have to worry about which technologies I'm using within this project. I don't have to worry about which events I want to track. like you literally plug it in, it figures everything out for you, goes through the codebase, analyzes it, and implements itself within it. This really is the future of how I want to install all of the upcoming tools within my code. But I'm not going to give it too much praise for now. Let's see just how well it actually does it. After the setup is done, the wizard will also offer to install a Post Hog MCP server. This lets your AI coding tools query your Post Hog data directly. For example, you can just ask it which screen has the highest drop off and it'll answer directly from your actual analytics. Install it as it takes just about 30 seconds. You can select the editor to install the MCP server for. Feel free to choose the one you're using. I'll pick one at random right now. And then you can choose which data pieces you want to give access to it. I'll just press A to toggle all of them. Keep it flashing and press enter to continue. And there we go. We've successfully installed Post Hog. By we, I mean the hog itself. We can check the Post Hog setup report for more details. Or we can just read more about it here. Here are the different events that it is tracking. User signed in when users successfully sign in with email and password via clerk. The user signin attempt failed. The user signed up when they actually complete the email verification and account creation with clerk. User signup failed. User signed out when they sign out from the settings screen. And subscription expanded. So here we can actually see which card they expanded, which is super useful information for us when we want to learn a bit more about that user. And then when they open the subscription details screen. Even though this is just a couple of events, it's actually more than I anticipated that it's going to have at this point in the app. Considering that right now, we just have our home screen. Later on, as we add more screens and more functionalities to the app, we can just add those additional events in. Now, back to the Expo terminal. I'll stop the application from running and then rerun it by running mpx expo start and pressing the letter R to reload it on my screen. we'll get a little issue saying unable to resolve post hog react native from app signin tsx. So the first thing that makes sense is to actually install this package post hog react native within our second terminal by running mpm install or mpx expo install post hog react native. There we go. It installed it. So now we can rerun our application with mpx expo start with a - c flag. So if you get a module resolution error after the wizard, it oftenimes means that the package didn't install during the wizard step. So just install it manually and restart with the clear cache flag. And we're back into the game. Now we can try to log out of our account and then sign back in as we know that's one of the things that Post Hog is tracking. So go ahead and log out. Then head back over here and sign in with your existing account. Enter your password and sign in. Then if you head back over to Post Hog, you'll be able to see the identify event right here and the installation complete. Then click next. You'll also be able to auto capture front-end interactions. We are using the React Native library. So Post Hawk can automatically capture clicks, submits, and more. And we can fine-tune what we capture directly in our code snippet. For now, we can capture everything. There's also heat maps that allow us to see where people are scrolling over and what they're seeing. And also the web vitals. Oh, there's also the session replays which allow us to track complete user flows as they're going through the application which is super useful for getting a more precise view of how people are navigating and interacting with our app. So, I'll definitely turn this on. For now, I'll skip connecting to additional sources. And we can then choose one out of two plans. The pay as you go plan does require you to enter a credit card, but it starts completely for free and it has a super generous free plan. You can go over 1 million events for product analytics, 5,000 session replays, experiment with flags, have surveys for your customers and also do error tracking. But if you don't really feel like entering a credit card for now, you can also start totally free and then move over to pay as you go as your app starts to grow. If you want to, you can invite teammates. For now, I'll skip that. And that's it. We are ready to go. So, let's see what the quick start guide will tell us. I'll zoom out just a tiny bit more. Immediately head over to settings and turn on the dark theme. I'm just going to turn it on to dark. Okay, that's much better. Now, going back to home, you can expand quickart and basically figure out what's happening within this dashboard. First, you can ingest your first event. We've already installed this, so I think we're done with that. Then, we can set up a proxy, which improves the data accuracy by routing Post Hawk through your own domain. You can totally set that up. And then, you can create your first insight. So, just click new. And in this case, we're tracking page views. So, click save. And that is our first insight. Now, we'll go through these later on, but what I first want to see is are our events actually coming to this dashboard. To be able to know that, you can go to activity and see exactly what is happening. While we were logged out, you can see that our user wasn't getting tracked. But now that we got logged in, you can see this user's email and some other information that we're able to pull out. In this case, we touched a specific view or clicked a button or we visited specific screens like the settings screen right here. So, what would happen if I went over to the settings screen and then reloaded? You can see that we touched a specific text or clicked a specific button and even expanded a subscription. This is the event that I wanted to see more about. And yeah, from here on you can use these events to create dashboards, build funnels, track retention, or set up feature flags to show different features to different users. And also take a look at the heat maps, session replays, and more. You can basically turn on all of these features, which are then going to show up on the sidebar. So this means that the analytics for our app are now live. The wizard set up event tracking. We connected it to real clerk users by identifying the user and the dashboard is now receiving the data. As we add more features later on, we can add additional events to Post Hog in a matter of seconds. So let's go ahead and commit as well as push this Posthog integration and run get push. Once you push it and before you proceed with the next lesson, go ahead and check out Posthog's homepage. I'll leave the link down in the description. It's super fun to play around with. And I love how they speak directly to us to developers. Like you can go ahead and check out the demo that basically explains what you can use Post Hawk for. And it's not just for mobile applications. I actually prefer using it on all the web applications and landing pages that I develop just to be able to track what people are doing on them. So yeah, that's it for now.
Subscription Tab
The subscriptions tab has been sitting here since the routing lesson doing nothing. So let's fix that. We'll build a screen where users can see all their subscriptions, add new ones, and cancel existing ones. It'll look something like this. And since we're not yet connected to the back end in this video, we'll use local state, but the patterns are identical to what you'd use with an API. You simply need to swap the use state for a fetch call and everything else stays the same. Now we already have this subscription card that we developed as a part of our homepage. So now we just need a full list with search and potentially filtering options to display it on the subscriptions tab. Let's go back within our IDE, open up our AI agent, and simply tell it to implement a searchable subscription list using the existing subscription card component and the dummy data from constants. This alone should do the trick and we can tell it that this can happen on the subscriptions screen. Press enter and let's see how it does. In this case, we're again using AI to do nothing we haven't seen before. We're basically letting it handle the repetitive layout that saves us about 10 minutes of typing the same patterns over and over again. So, as usual, it'll go through the codebase, figure out that it needs to edit the second screen right here, the subscription screen, and it'll use the existing subscription card component alongside the constants and just display them right here alongside a simple search. So now if you reload your Expo server, you should be able to see this new screen that now appears on subscriptions and you can search for subscriptions like this. So now let's search for something like Canva and it appears right here. But we have a couple of different problems. The first one is that when the keyboard pops up, it doesn't actually bump our content up and rather it hides it. So that's the first thing. The second one is that the subscriptions text at the top and the search subscription text also are white whenever we type something. They should be black so we can see them. So I'll simply tell it to fix it. Great. But a couple of problems. The subscriptions heading at the top is white. It needs to be dark. Same thing goes for the text when we type into the input where we search subscriptions. And third thing is to bump the content up above the keyboard once the keyboard pops up when we start searching. So you can tell it something like this. Hopefully it'll be able to quickly implement it and we'll have our second screen in a matter of like 2 minutes instead of 10 or 20 that it would take us to manually develop it. There we go. It says that it did it. So you can see the subscriptions text is now dark. And if I search and type something like Canva, you can see that now we can see the actual content, but it still doesn't bump it up above the keyboard. So I'll say the cards are still being hidden by the keyboard. Fix it. And in this case, it needs to properly utilize the keyboard avoiding view. I can show you how that works. If you head over into that's going to be the tabs page and then subscriptions. And you can see how it implemented it right here. It basically used a flat list and passed the filter data right here. So when we search for something, we basically filter it by name, category, and plan. Pass over the data into the flat list. And that's how you filter things within a list in React Native. And then this flat list is more or less the same as the one we're using on the homepage where we render the subscription card. And that's it. And now after the last time I asked it to fix it, it basically removed the keyboard avoiding view completely since the flat list handles keyboard interactions natively through scrolling. So basically it added the keyboard dismiss mode on drag which will remove the keyboard when you drag the list and the list will automatically scroll to keep the content visible when the keyboard appears. So now if I search for something like Canva, you can see it's there. But if I just have the keyboard open and if I scroll up and down, the keyboard is going to dismiss and we'll be able to see our screen. So with that in mind, we now have our second screen. You can take a second and go through this component to make sure that you understand every single part of it that was written, but I'm sure you will because you already created your two flat lists back on the homepage. Great job.
Create Subscriptions
Right now, that plus button at the top right of the screen is just decorative, but in this lesson, we'll make it open a form where users can create a new subscription. And since this is a form with a couple of different inputs that we've already seen on the odd page, it's the perfect use case for AI. The repetitive part is the form layout and state management. And the interesting part is actually wiring it into the home screen. So, in the video kit link down in the description, I'll prepare a detailed prompt for you that you can copy and then use right here within your AI agent. I'll keep the previous changes. Start a new chat and simply paste them here. We are going to tell it something like this. Study the entire codebase paying close attention to the existing design system, the constants, the icons, and the home screen. Then create a new create subscription model page that is a react native model that slides up from the bottom with a transparent overlay. It has a header with new subscription and it has four different fields name, price, frequency, and category as well as a submit button. On submit, it creates a subscription object with all of these different things that a subscription needs. And we can also use the keyboard avoiding view for iOS so we can type into it properly and it resets the form after the subscription. Then the plus icon in the home header should open this model when tapped. When a subscription is created, add it to the beginning of the subscriptions list on the home screen. The new subscription should immediately appear in both the all subscriptions flat list and the home screen. Perfect. As I said, this is a super detailed prompt and typically you could have gone just with typing, hey, build me a subscriptions create form, but I like to keep it a bit more detailed for you so that I know that you get the same output that I did. So, let's give it a second until it reads all the files and implements it. And then we'll go through everything it did together so we can understand 100% what's happening and ensure that we would also be able to replicate it manually if we needed to. And there we go. The component has been created and integrated within the home screen. Oh, it also added post hook tracking for subscription creation, which is super handy. So, let's go ahead and test it out together. I'll press the plus icon. And you can see a new model appearing at the bottom. Now, let's test it out. Let's say that I just subscribed to Spotify with a price of $5. 99. And that's going to be a monthly price. Uh, it's going to be under entertainment. and I can create a subscription. And you can see Spotify shows up right here at the top. Another update we could make, and I think this would be super simple with AI, is to just ask it to find a huge library of different icons online and that it automatically matches and shows the icon based on the name that we used for the subscription. That's going to make the app so much better immediately, but I'll leave that as a challenge to you. Now, one thing I can notice is that this new Spotify subscription isn't showing in the all subscriptions list. So, let's fix that with a quick prompt by telling it to make sure the newly created subscription also shows on the all subscription screen. And let's see if it can do it. Basically, what it needs is a shared state solution. So, it's going to look at our current state management setup. Zastan is available. So it'll create a simple Zestan store for subscriptions so that it displays unified subscriptions both on the subscription screen and on
PostHog Custom Events
the homepage. But while that is happening, let's take a look at the newly created create a subscription model component to see what it actually is. And by having watched this course for so long, would you actually be able to recreate this on your own? Because I'm sure you would. So first we have some different types for the category, for the frequency, for category colors, some things that we want to save right here so we can use them later on. Then we create the create modal subscription that accepts a couple of props. Is it visible? What happens when we close it? submit? And then it takes in a couple of different things. In this case, it is taking the name, the price, the frequency, and the category. So let me type them one more time. In this case, I'm going to go with something like YouTube premium and we'll do $9. 99 on a monthly level and it's going to be entertainment as well. So, what happens once we write all of these values is we turn the price into an actual number. We get the current date and we figure out when we want to renew it either by one month or one year. Then we form this new subscription by adding it the ID uh passing in the name, the value, the currency, the frequency, the category, the status, as well as the start and renewal date icons too. In this case, we're using the icons. wallet. I think we could have used some other icon. So, I'll head over into that's going to be assets icons. And maybe we could have used some kind of a dark icon. Well, for the time being, we can just use the plus icon for all of them, indicating that we need to implement a custom system where it's going to automatically figure out uh the icon to change. Again, that's a little task for you to implement. Then we add the billing frequency and the color. And then we call the onsubmit and pass in the new subscription. After that, we reset the form and close the model. The model in React Native works in the same way it does in React. You basically give it a visible prop which decides whether it's being shown or not. And you also have some different built-in animation types like slide, none, or fade. In this case, it's sliding from the bottom. And what you can do to close it. Then we use this keyboard avoiding view which makes sure to bump the content up when we open up the keyboard. And then we have different pressables. Pressible is basically a button in this case. And this pressible here at the top means that if we click outside of the model, it'll close it. This one here means that if we click on the X button at the top right, that it'll close it. Then we have the scroll view to make sure that the entire view is scrollable. And then within it, we have different views for different text inputs. The first one includes a piece of text called name and then the actual text input that handles the value and changes the name. Same thing as in React. And then we repeat that a couple of times. For the frequency and the category, we don't use inputs, even though you totally could, but it's much easier to do it with some kind of a picker. So instead, we create a pressable and allow the person to literally press on the option they want to choose. We do the same thing with the categories. And finally, there's a button called handle submit, which actually submits the subscription. So let's try to submit this new subscription. You see we added YouTube Premium. And if you head over to subscriptions, you'll see that it also got added right here. Perfect. This means that we have successfully added the ability to add new subscriptions. So with this subscription screen added and with this new model that we've added to add new subscriptions, that's already a lot of code that would be good to check and merge over to main. So I'll open up the terminal and run g get add dot get commit-m implement the subscriptions screen and the subscription create modal and then run git push. Then back on GitHub we'll be able to recognize that our dev branch had recent pushes. So we can just create a pull request. And then let's see what code rabbit has to say about it. And while Code Rabbit is doing its thing, there's something else we can focus on. Let's improve the depth of tracking. The post hog wizard set up a baseline tracking before. But the create subscriptions feature we just built is the action that matters most for a subscription management application. So let's track it. This time I'll show you how to capture events manually. So in the subscription handler that's going to be right here under components create subscription model and where we handle the new submission right after adding the subscription. So that's going to be right here. We also want to get access to post hog. So simply say post hog which is going to automatically import it at the top from source config post hog. This is your specific post hog instance. and then say dot capture which is one of the methods available on this post haul client. The first parameter that it accepts is the event name. So which event you want to track. In this case, we want to track the subscription created event and you can also pass some additional optional properties like in this case we can also track the subscription name, the subscription price, subscription frequency and subscription category. So simply paste those values in. The event name basically tells you what happened, but the properties give you the context. Says that someone added a subscription, but with properties, you know, they added Netflix at $15. 99 monthly under entertainment. Or you can also figure out the average price of all of the subscriptions that these users have. So let's rerun our application. Create a new subscription. Let's do Netflix at $15. 99 monthly and that's going to be under entertainment. And I'll create a new subscription. You can see it got added to our application. And now if you head over to the Posthogs dashboard under activity, you'll be able to see that this user right here created a subscription. And if you hide all the additional Post Hog properties, you'll be able to see the properties belonging to this event. We get all the data. So let me show you how you would build a funnel out of our current application. You can basically head over to create a new insight and you can either go for trends or a funnel and now you can add different steps. The steps can either be screens or specific actions. I'll start with events of user signed in. That's the first thing that we want to see. Now we want to figure out the amount of users who signed in and then created a subscription. So I'll say created. You can see that the event automatically pops up right here. And then maybe finally the amount of people who created a second subscription. This is the amount of people who came back to use the app more. And here you get the full funnel. You can see that one person signed up, one person created the subscription, and the same person created a second subscription too. Of course, if we had more users, these metrics wouldn't be as good as they are right now. It's very hard to have a 100% conversion rate within your app, but I think you get the idea of the overall possibilities and the amount of things that you can track with Post Hog. You can track different trends like the amount of users that have signed in or signed up or created subscriptions in a specific trend. You can track funnels. You can track retention like are those users coming back? Are they not? And even the user paths by screens. For example, you can see how they're moving from the home screen to settings and so on. Of course, we just recently added subscriptions. So, we don't have many users coming to our app right now, but just imagine if we had more users using our app, we would be able to track all of these different pieces of data with Post Hog. So, yeah, we basically use that Post Hog wizard to set it up in a couple of minutes, which also added initial event capturing for our app up to that point. But now for every new feature that we add, you can either ask your AI agents to add postul tracking for new events you're adding or you can capture them yourself manually like we've done just now. And that simplicity and the developer experience is why I choose to use post hog for any kind of website that I create. So let's also commit that post hog change by running git at dot get commit-m implement post hog tracking for subscription created and running get push. Perfect. Now we have a bit of a better idea of how users are using our app. And back on code rabbit's side we have a couple of actionable comments that we can fix. This one says that the upcoming tab, this one right here, is ignoring the new storebacked subscriptions, which is true because we added Netflix, but the Netflix isn't going to show up here. So, that's a little update we can make. Oh, this one is good. Reset is executed before logout succeeds, which can desync the session versus analytics identity. So, instead, we first run sign out and then we run post hog reset. Now, we can fix all of these comments manually as before, but now I want to show you something different. There's also a prompt for all review comments with AI agents, which basically takes all of the comments and in one message explains the AI what they should be doing. For example, this is the one that I showed you. The upcoming carousel is still using the static upcoming subscriptions instead of the live store. So, we need to update the component to read the dynamic data from the use subscription store and pass that array into the carousel. In the settings, the analytics reset is called before sign out. That's also the one we've seen. And a couple others. So, you know what? Let's give this a shot. I'll actually copy this entire message, open up Juny in a new chat, and paste it all in. Let's press enter and give it some time to work on all of these changes. And there we go. Within a couple of minutes, our application code fixes have been applied. Added param sanitization in our layout to prevent sensitive data leakage. Added post hook tracking to handle verify path in signin for complete coverage. Replaced upcoming subscriptions static data with a new one. So now when we add a new subscription right here, I'll just do a test for now with five and create. Replaced upcoming subscription static data with dynamic store data in index. moved post hog reset after sign out in settings with error handling and so much more. So I'll keep all of these changes and just run a push. I say get add dot get commit-m implement code rabbit suggested fixes and run get push. This is actually the first time that I'm trying this prompt for all review comments with AI agents feature by Code Rabbit. And it looks like they're also working on autofix, which is going to allow us to well automatically fix all the changes. Either way, this is amazing. So, I'll go ahead and merge this pull request with these two new features, or rather one new screen and one new feature that we added. So, with that in mind, our application is now getting very close to feeling like a real product. Sure, there's still some improvements to be made, like the one with automatic icons for different features that we add this missing insight page, which I left as an exercise for you to try to implement it. But when it comes to learning React Native, you've done your part. You know how to create flat lists, views, and more. The only thing that other courses don't teach you, how to actually deploy this project to the App Store. So, let's do that in the next lesson.
EAS Build & Deploy
Throughout this course, we've been running recurly through Expo Go, but that's a development tool. It's great for building, but your users aren't going to download Expo Go to use your app. So, to turn Recurly into a real standalone application with its own icon on the home screen, we can use EAS, Expo Application Services. It takes your product, builds native binaries in the cloud, and then gives you installable apps for both platforms. And yep, you'll be able to upload your app to the Play Store and the App Store while automating your CI/CD process and tracking your builds in real time. Oh yeah, and you can also instantly deliver live updates to your apps while they're published in the store. So, click the link down in the description and get started for free. There's different pricing options, but free is going to be more than enough for what we need for now. And then we can head over to the build section, which essentially is just a terminal command. So back within our application, open up your terminal and run mpm install-g to install it globally. EAS CLI. So before you publish anywhere, you need developer accounts on both platforms. Apple developer program 99 bucks per year. You need an Apple account with two factor O enabled. You can choose whether to enroll as individual or organization. Individual means your personal name appears on the app store whereas organization requires a DUNS number and the organization name becomes the seller name. Once you enroll, you'll get access to the App Store Connect, Test Flight, and all the submission tools. Google Play Store is cheaper, 25 bucks for a one-time fee instead of 99 bucks per year. Much simpler setup. You sign in with Google account. Create a developer account. Choose personal organization and then complete the identity verification. Then log into your Expo account by typing EAS login. It'll ask you for your email and password. So you can just take a look at which one you used here under the user settings. For me, it's JS Mastery. And for the password, if you forgot it, like I did, you can head over to expose settings and then scroll down to change the password right here. It's possible that I don't remember it because I initially signed in through Google. Once you know your password, you can just type it in and press enter and you'll be logged in. So now to verify, you can just type EAS who am I and it'll tell you your username and email. Now we are ready to configure the project by running EAS build colon configure and press enter. This will create an EAS. json file for you. So you can say yes, go ahead and automatically create an EAS project. It'll link it, but it's saying that our project uses dynamic app configuration. So EAS project ID can't automatically be added to it. So to complete the setup, set extra EAS project ID in your appconfig. js js to this. So, let's do just that. I'll copy this piece of code. Head over to our app. config. And where we have expo, we want to get into the extra. And then we want to add this EAS part right here within it. Perfect. And let's make sure to move it up a level because it needs to be inside of this extra configuration object. And since we're not in JSON, but rather in just a regular JS file, we can remove these additional string signs. So now that we have this, open up your terminal and rerun the same command again. EAS build configure. Perfect. Now it's asking us which platforms would we like to configure the EAS build for. So I'll say all. And that's it. Our project is ready to build. We got a new generated EAS. json file. You can take a look at it right here and you can see different information about the build. Now, before we proceed to the build, head over to your app. json. That's your main app starting point. And here, let's make sure that our name, slug, version, and all the other important information about the project are correct. So, instead of react native recurly, I will just change this over to recurly with a capital R. And the slug can be set to recur like this. The version 1. 0. The icon needs to be pointing to our actual icon. png, which isn't going to be the expo one, but rather we got to switch it to the proper app logo, which is going to be coming from assets. And then under icons, you can find the logo right here. Assets, icons, logo. png. Perfect. Then you can search for your splash screen right here under plugins expo splash screen. And let's verify this one by heading over to assets images splash icon PNG. So that's going to be assets images. And here we can see the splash icon. This is the default one. But instead we could be using the ones from here. You can either use the logo or maybe we can use this one splash pattern which looks much better with our application. So, it's going to be splash-pattern. png also under images. Perfect. Also, under iOS, go ahead and add the bundle identifier, which is going to be com dotyourname. recurly like this. Here you can put your personal name like Adrien. And we'll do the same thing for Android under dot package. And that's going to be com. adrien. recurly. These identifiers matter because they are how the build system and stores uniquely recognize your app. So if you ever submit to the App Store or Play Store, you can't change these after the first submission. So pick something you're happy with. Okay. Now heading back over to the newly generated EAS. json. And now let's build it. You can use the EAS build-platform iOS-profile production to build it for iOS or you can use EAS platform Android to build it for production or you can also build both at the same time. Since I've been testing on iPhone this entire time, I'll go ahead and run EAS build platform iOS profile set to production. Press the letter Y to let it know that iOS app only uses the standard or exempt encryption. And we have to add this line to our app. config. js. So I'll copy it. Head over to app. config. js and add it right here outside of Expo. And then it's asking us if we want to provide our Apple account credentials to be able to generate all necessary build credentials. So select yes. And then we'll have to enter our Apple developer account ID. Then you'll have to press enter a couple more times. Validate your device. Enter the six-digit code that you get into your device. And that's it. Project files are getting compressed and are being uploaded to EAS build. Now, let's wait for the build to complete. Now, it looks like the build failed. There's an unknown error, but thankfully we can see the logs under our Expo account. So, if you open up your Expo under this specific project and then head over to builds, you'll be able to see this build errored out. And then you can see that it failed on the dependency installation step. Now, whenever I face some of these errors, I typically copy the entire message, share it with an AI agent, and ask for the fix. But in this case, it looks like it's pretty straightforward. The mpmci can only install packages when your package and package lock are in sync. So, we have to update our log file with mpm installed before continuing. So, what we can do is run rm, rf, node modules, and package log json, which is going to completely remove all the dependencies. This might take some time as we have installed quite a few dependencies as we were developing this project. Then just run mpm install to reinstall all the dependencies. There we go. And now that we updated it, we can also commit this new log file to GitHub by running git add package- lock. json JSON git commit-m fix sync package lock and then we can just run git push and then we are ready to rerun the build by running EAS platform in this case iOS profile production once again you'll have to go through the same process so I'll just skip this for you and we're back at compressing the project files uploading the EAS build and finally waiting for build to complete This can take anywhere from 5 to 15 minutes. So, let's give it some time. You can also head back to your Expo dashboard. Head over to your project. And here you'll be able to see the project's activity, the artifacts. You can customize the layout. And later on, you can also track the project insights like app usage trends, active users, and the workflow performance. But what we care more about right now is the activity. Specifically, this build that is going on. You can of course track it in real time as it's waiting to start, then spinning up the build environment, reading our configuration files, and now hopefully it doesn't get stuck at the dependency installation process. There we go. Looks like we're past it. It's reading more of the app config, running the expo doctor, preparing credentials, pre-building, and finally installing the pods. EAS here uploads your project to cloud build servers, compiles the native code, handles signing credentials, and then finally produces installable binaries. No Mac needed for iOS builds and no Windows needed for Android. That's one of EAS's biggest advantages. And when I say that EAS manages signing credentials, that's really a big deal. On Android, it handles the release key store. And on iOS, it handles provisioning profiles and distribution certificates directly through your Apple developer account. That's a huge amount of manual pain removed. And looks like we're getting far into this build process. And there we go. The fast lane was run, credentials were cleaned up, and the application was uploaded to the archive. Now, if you head back over to your terminal, you'll be able to see that your build has finished, and you can click right here, which will just download the IPA file. Once the build has finished, you can submit it to the store by running EAS sububmit-platform and then either Android or iOS. And now, press enter. It's going to ask us what would we like to submit? A build from EAS or provide a URL from the app archive. provide a path to the local app binary file or a build ID to identify a build on iOS. In this case, I think we can go directly from the build on EAS, which it'll recognize right here 2 minutes ago as it completed. So, press enter. You'll have to sign in with your Apple account once again, and it'll schedule an iOS submission. You can see the submission details right here under deploy submissions. It's currently in progress. It spun up the build environment. It downloaded the build and is now uploading it to the App Store Connect. And even before it finished, if you want to take a look at your app within the App Store Connect, you can click this link and check it out. And then the job is complete. So back within our IDE, you can see that our binary has been successfully uploaded to the App Store Connect. It's being processed by Apple. and you will receive an email when the processing finishes, which usually takes about 5 to 10 minutes depending on how busy the Apple servers are. When it's done, you'll be able to see your build right here. So, when you sign in to the Apple Store Connect, you can head over to Recurly. And here you can see that it's ready for submission. So, when you're ready to actually distribute it to the users, you can just enter all of these details, promotional text, the screenshots and previews, the keywords. These are the fun parts, right? And then just submit it for Apple team to review. But for now, head over to test flight here. We first need to fix this missing compliance. So click message and choose what type of encrypted algorithms does our app implement. In this case, it's just standard. Is your app going to be available for distribution in France? Well, I guess not necessarily. No. So we can just click save. Then once that is done, we can have an internal and external testing teams. Currently, we have an internal right here. And under builds, you can see 1. 0. 0 available for testing. Then download TestFlight on your phone to be able to test these applications before you actually publish them. Then you can add yourself as a tester right here and it'll appear directly within your TestFllight application. So yeah, as soon as you add yourself, you'll actually be able to test your app within Testlight by installing it for real on your device. And then the next step is to actually add it for review and release it to the app store. EAS will handle the upload, but the store review and release management will still happen either in Apple or Google's own dashboards. But yeah, Recurly is ready to be ran as a real app.
Closing & What's Next
So just take a second and look at what you did. You started with an empty folder and a terminal and now you have a production mobile application. So let me walk you through exactly what you learned because it's more than what you might have realized. You learn how React Native works under the hood and why Expo is the standard. You then build a filebased routing system with route groups, tab navigation, dynamic routes, and shared layouts. You styled the entire app with Native Wind, custom themes, and a design system that maps directly from Figma. You then loaded custom fonts and wire them into Tailwind utilities. You built a production home screen with two different flat lists on the same page, one horizontal, one vertical. Reusable components and proper scrolling patterns. You then implemented full authentication with Clerk. sign up, sign in, email verification, session management, and protected routes. You used agentic development to let AI handle the repetitive implementation while you focused on architecture. You integrated Post Hog for analytics, both the wizard setup and manual event tracking with user identification. You then developed a functional subscriptions create modal that allows you to create new subscriptions to your app. And finally, you deploy the whole thing to real devices through EAS build and submit. That's not just a tutorial. It's the entire workflow you'd use to build and ship any mobile application. So with these skills, you're not just limited to subscription trackers. Think about what you can build. Almost every single application on the planet has the same screens. a splash screen, authentication screens, a homepage, this beautiful bottom navigation, some app specific screens, and then the settings or the profile screen. The point is once you know how to structure a React Native app with Expo, the project is just the domain, but engineering is transferable to every single app you want to build. Now the app currently uses local state like these subscriptions that we can create exist in memory and then reset when you restart. To make this a fully production app with real data persistence, serverside logic and user specific data you need a backend and I already created a separate backend course that teaches exactly that. Same recurly project Node. js Express MongoDB and API design is covered within this course. And I already created a new GitHub repo for it that can be fused directly within this mobile app. So if you'd want to see me connect this React Native app to that backend live, creating subscriptions that persist, fetching real data from the API, handling serverside renewals, drop a like on this video. If this one hits 20,000 likes, I'll make part two where we wire everything together. Don't forget to check out jsmastery. com and specifically the ultimate AI development course weight list where you can enter your email to stay uptodate with the upcoming lesson releases or you can already pick and choose from many of the courses already completed on the platform that you can get access to through this nicely designed platform. So thank you for building with me in this course and now go and ship something.