React Native Full Course 2026 | Build, Publish and Monetize a Full Stack Mobile App to App Store
Skills:
React90%
Key Takeaways
Covers React Native fundamentals, building, publishing, and monetizing a full-stack mobile app, including setting up authentication, implementing subscription tracking, and publishing to the App Store
Full Transcript
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 Next.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 back-end 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, NativeWind to style your React Native apps using Tailwind CSS, Clerk for auth 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 roadmap 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 hire ready, you'll love JS Mastery 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 Next JS, testing, animations, JavaScript, SQL, 3.js, 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. Before React Native, building mobile apps meant maintaining two completely separate code bases. One for iOS in Swift and one for Android in Kotlin. 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 changed that. One code base 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 Next.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 to 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 team'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 Next.js, 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 through 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 sign up, sign in, user management, and session handling with just a couple of lines of code. What used to take me days of wiring up auth providers, managing tokens, and building login screens now literally takes minutes. For Recurly, we'll use Clerk's custom auth flow 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 Jetpack 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 off, 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 off section of the build. Now, PostHog 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? PostHog 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 onboarding, feature flags to roll out new features to only a percentage of users before going live, and error tracking to catch crashes the moment they happen. PostHog 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. PostHog'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 analytic section. And we'll also use CodeRabbit for AI-powered code reviews. When you push code to GitHub, CodeRabbit automatically reviews every pull request. It reads the diff, understands the context of the 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 the React Native and features you're 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. 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 worked with React GS 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 React GS to help you understand the similarities, but also the differences between the two. So, super quickly you'll understand the 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-like 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-like syntax as in React. You can set the font size, color, and weight using the style prop. React Native also offers a style sheet 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, Native Wind came into the picture allowing you to write Tailwind-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 div element 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 overflow 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 device's 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. It's 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 PNGs, 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 the 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 models? Well, thankfully, React Native has components for that, too. It's called a model. Just import it from React Native, set the 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.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 models, 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. 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 regularly. 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, high-quality 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 production-ready 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 through 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 IDE 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 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 Juni 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 NPX create-expo-app @latest --template and we're going to use default @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 Go application for your phone. Then, back in 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 an 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's file-based routing instead of manually written app.tsx. 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 Expo's 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 Expo's base TypeScript config. And two things matter here. Strict is enabled, which catches more bugs at compile time. And the path aliases that we set so that we can import with at forward slash instead of using the full relative path like dot dot slash dot dot slash and so on. So, this is going to be super handy. Then, there's the package.json log JSON which locks exact dependency version, .gitignore 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 _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 slash 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 npm run reset project which is one of the scripts within our 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 control 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.tsx to edit the 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. 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 NativeWind 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 NativeWind V5, Expo 54, and Tailwind CSS, but since we have an existing project, let's do it 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 NativeWind and its peer dependencies, Tailwind CSS, React Native CSS, React Native Reanimated, and React Native SafeAreaView. or through npm. In this case, let's proceed with Expo by running npx expo install and then install all of these additional dependencies, which are essentially going to do the same thing. Simply run npm 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 plugin 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 get. And then we can simply copy and paste this block of code within it. We also need to create a globals.css file or global.css and add the following tailwind 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 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 NativeWind. 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 app/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 devDependencies. We're going to set the Lightning CSS to 1.3.1 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 NativeWind-env.d.ts, also at the root of the application. That's going to be NativeWind-env.d.ts. 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 NativeWind part. It should show how className props can be used, Tailwind utility classes, color utilities, and also typographies. And if you see the style text centered in a white background, NativeWind 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 app/global.css, and then run R to reload one more time, and now we can see welcome to NativeWind. 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 npx 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 npm 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 NPX Expo start with a dash dash 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 Land, 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, orangish. 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 dot CSS file. It'll include this Tailwind 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 Re Curly'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-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 NativeWind, 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 the pro tip, if you're starting with a new project and you already know that you're going to be using NativeWind 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. As I mentioned in the crash course, routing in React Native with Expo Router works almost identically to Next.js. The file name becomes the route path. No manual navigation config, no router 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 RNFE, 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 slash} onboarding path. Out of the box. To test it, you can go back to your home page 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 onboarding. 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 device's 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 Next.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 auth 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 parentheses, 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 parentheses and end with a closing parentheses and call it auth. Then, within auth, create two new files, sign-in.tsx, where you can run rnfe, and this one will be called sign in, and it'll have a link component, which is going to be pointing to auth like this. So, {forward slash} auth route group, and then {forward slash} sign up, 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-up.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 auth folder is wrapped in parentheses, the word auth will never appear in the URL. Well, if you were in the browser, these routes will resolve directly to sign in and sign up pages, not auth/sign in. 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 home page and adding to 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 sign in pointing to {forward slash} auth in parentheses {forward slash} sign in. 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 control 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 the concept of a 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 sign in, you'll see auth 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 auth group. So, head over to the auth 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 sign in, we were hoping to see the header completely hidden. But, that isn't really the case. What is the case is that without it, you would have two different headers. The one from the home page and then the one coming from the second layout. So, this one only hides one. The header for the auth screens, but you'll still see the root layout's header on top. We can hide that too by also applying the same property right here of screen options header shown set to false. And now we get a nice black screen. Very clean. The important takeaway here is that every screen inside of the auth now shares this auth layout. Later, when we build real auth UI, this layout is where we'll add shared styling, background colors, or animations that apply to all auth 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 home page, 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. A route group is simply a folder wrapped in parentheses, so that's going to be called tabs, like this, and it's going to be within the app folder, not within the auth folder. Then, inside it, create a new file called subscriptions. tsx, where you can run RNFE to quickly spin that component up. And then, we'll create another one, which is going to be called insights. tsx, also run RNFE. And finally, also add the settings page. So, right here, settings. tsx. And run RNFE. 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 home page, since index.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 auth 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 detail 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't 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 in .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 recreated it's not going to show a snail. Nope, this snail is still there. That's totally okay. So, the bracket syntax works in the same way like in Next.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 RNFE. 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 de-structure 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 home page and it'll say go back. Now, we can add these links to test from the home page. So, heading back over to our tabs. index.tsx Right below these links, we can now add two more. One link pointing to a /subscriptions/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 home page. 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 device's 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 powered 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 parentheses 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 code base 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 Recurly 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 Juni powered by JetBrains' AI chat which allows you to complete code, generate 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 of 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. It'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 it 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. 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 home page, 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'll be automatically returning it, there's no need to explicitly add a return. We can just use the parentheses, 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.screen. 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 want it to be 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 be 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 5, 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 we'll have the settings 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 nice-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, Expo's routing system auto recognizes it. So, we can specifically set it to null by duplicating tabs.screen, specify the route to subscriptions, and then /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 tabs.screen 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 tab 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 code base and the assets. So, click on the assets and then download this zip 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 home page, which we'll 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 npm i 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. tabs .screen. 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 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-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 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 get a peer. 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 hard code 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, npm i 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 device's hardware elements take up. The bottom inset on an iPhone 15 is around 34 pixels. But on an older iPhone with a 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 inset 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.tab And these components are coming from the theme. So, you can import them as colors and components coming from @/constants/theme. And then here, under insets bottom, we can also add those custom values, tab bar.horizontalInset. Now, let's reload the app, and you'll see that we have a little error right here under math.max. 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 math.max 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 tab bar.height. We can also add a margin horizontal, which is going to be tab bar.horizontalInset. We can add a border radius, tab bar.radius. 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 set to zero as 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 {dot} height divided by two minus the tab bar {dot} icon 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 {dot} icon frame, the height to tab bar icon frame, 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're returning a view that says tabs icon with a view class name tabs pill, 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 our 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 NativeWind, RN Safe Area View. And this will allow you to pass the styling. And this styled part is coming from NativeWind. 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 NativeWind 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. So, take a look at that. We have our home page with some navigation. The onboarding and the two auth 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 get checkout -b to create a new branch and switch to it called dev. Then run get add dot to add all the changes and let's commit the changes by running something like git commit {dash} m feet as in features we implemented project setup native wind routing and tab navigation. Press enter. We've done a lot already and then run git push you 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 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 walk-through 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 will 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 is 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 code base, 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, here, and here. And with that, we can go ahead and push those little fixes. So, I'll type git add dot, git commit dash m, implement fixes, and run git push. Automatically, GitHub and Code Rabbit will recognize these new changes, and we can just go ahead and merge this PR. 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. 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 plugin 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 dot slash assets forward slash fonts forward slash plus Jakarta Sans regular.ttf. And we'll actually implement a couple more like bold, medium. We'll also do semi-bold. 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, 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 destructuring fonts loaded and 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 dot slash assets forward slash fonts forward 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 use 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 .hideAsync. 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.hideAsync, 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 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: --font--sans. You want to make that to sans 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 sans bold like this in your JSX, NativeWind will automatically resolve it to sans bold 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 NativeWind text, let's change it to some other color, maybe text-primary. We can make it a bit larger like 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 home page. Now, this is it with using just the 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-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 home page 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 sans regular for the text body, font sans bold for headings and font sans 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 {dash} m implement fonts and then get pushed. 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 home page. 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 expendable 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 callout in 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 NPM 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 home page that will 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 home page. 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 home page. 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 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 @/assets/images/ avatar.png. 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 often time find from a library or maybe in previous times a 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 the Juni do all of that for me by opening up Juni and telling it to create a currency formatting function in lib/utils.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 Juni can create that new folder and 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? Intl.NumberFormat is a built-in JavaScript function for locale-aware 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 home page from top to bottom. Open up the app, tabs, index.tsx and let's start with a header showing the user's name and avatar. And then the balance card showing total monthly spend. 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 PO5. 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 use 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, dot avatar, and a class name set to home dash 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 to 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 dot name. 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 Adrian 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 {dot} 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. dot amount. If you're rendering 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 parenthesis 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 .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 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. Uh later, when we connect it to the back-end, 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 code base, 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're 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'll 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 home page, let's also develop the second one, which is going to be other 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 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 hit 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-head, below it a piece of text that has a class name of list-title, 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-action, 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 section header with a title and a 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 destructuring 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-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 de-structure the currency from the props. Perfect. 5.99. 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 two days left and I can see that my WebStorm 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 one 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 we 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. They're 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 tappable 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 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 RNFE. And let's simply import it within the home page 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 a 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 sub card, 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 sub copy. 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 sub title. And then, we can simply render the name coming from props. Finally, below this view and another view, but still within sub head, 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 destructure 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 home page and pass some of the props, such as dot dot dot home 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 a 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 deconstruct 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 sub card 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 the 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 ellipsis 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 imported from React Native. Pressable 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 pressable, but rather we're going to pass it as a prop as we keep track of which one do we click. And based off of it, we can make some changes. Such as we can always give it a sub card class name, but only give it expanded class name if it's expanded. So, it's going to be if expanded, display sub card expanded, else display BG card. 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 sub body and a text. And you can see that now the text appears as we click on it. Pretty cool. Now, instead of simply saying details go here, let's render another view that'll have a class name set to sub details, 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 ellipsis mode set to tail, so it truncates from the end. And here, we can render the payment method {question mark} dot 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. 1 2 3 and 4. 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'll 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 {underscore} 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, unclick 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 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 home page. 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 get add dot. Get commit {dash} m implement the home page and run get 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 a PR. 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 definitions, data models, utility functions, dummy data, and Tailwind styling. The home screen now renders a data-driven flatlist-based 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 font quicksand 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'll 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 auto hide async call. Without calling splash screen prevent auto hide async at module load time, the splash screen may auto hide before fonts finish loading, causing a visual flash. The standard Expo pattern requires preventing auto hide 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 auto hide a sync. Perfect. So, this one is fixed. We can go to the next one. Fix section title grammar for user-facing 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 Juni. 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 Juni is going to one shot 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. Ooh, 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 coded or you coded with AI agents. And finally, the last one is a minor issue. 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 dash m implement code rabbit suggested fixes and run get push. The changes will automatically get recognized and we can merge another PR now including our full home page. This has been an absolutely amazing lesson and I can't wait to dive into the next one. Up to this point, we've built routes, tabs, style components, and a complete home screen UI. Everything so far has been client-side 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 off 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 Next.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 new .env 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 sign-up 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 auth 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 to 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 Jetpack 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 deploy these projects 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 ENVs. In the next lesson, we'll integrate the Clerk SDK into our Expo app and build the actual sign-in and sign-up screens. 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 sign-up 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 auth state. Every single one of these steps is documented in Clerk's official quick start guide. It's procedural and well documented. 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 the 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 quick start docs, you'll notice they have this copy as markdown, and they recommend and have buttons for chat GPT, Claude, 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 Juni 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 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 we want to install Claude 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 code base and attached Clerk documentation to implement a complete custom Clerk off flow for this Expo app with production grade sign up and sign in screens with logic, validation, and navigation. The UI must strictly match the existing app design system and NativeWind patterns. Use the attached design only as layout inspiration, but keep the off 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 JetBrains' AI chat doesn't yet support images. So, a quick workaround is to go to any kind of an image upload service like ImageBB 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're 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 plan for us to start actually implementing it. This is what I like to call spec-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 off layout, build the sign-up screen, build the 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 code base. 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 auto fill it right here. Oh, the auto fill was super quick. I'll actually enter a strong password three times 123 and click create account. It's going to say creating 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 data breach. 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. 791 493 Verify email. And we are verified, and we get navigated over to our home page. 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 Adrian JS 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 JetBrains AI chat with Claude 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 Clerk 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 re-authenticating. 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 auth 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 home page. 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 sign in 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 sign in. that password, as well as other sign in methods, such as sign in 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 inline and below the relevant inputs. You can see that right here 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. Sign up 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 called the SDK methods in the right order. Now, what if I want to quickly log out so that we can check the auth page one more time because it's so good. Well, I'll open up Juni one more time and simply tell our AI agent to add a log out 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 sign in page because we already created our account, if I remember 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 home page, 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. 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 code base, 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 spent 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 waitlist 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 auth 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 {dash} 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 auth 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 of the root level, implementing complete sign-in and sign-up flows with email verification, and multi-factor 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 auth state loads and it checks whether we are authenticated or not. If we're not authenticated, it redirects us to the auth sign-in 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 multi-factor is turned on, we also need to send over the verification code. If they're already authenticated, we simply bypass the auth, 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 untracked ENV build. 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 .env.example where it says replace me, and then it mentions that this ENV file mustn't be committed, and it added it to the .gitignore. 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 our .env, 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 auth." 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 auth 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 Genie 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, auth and fonts load in parallel. So, we can check it out right here. We use the use auth, 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 Code 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 always true. Hmm, 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 sign up. 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 that 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 sign up. Let me fix both. This This amazing. I love it when I set up amazing AI agents, in this case Code Rabbit and Clerk's MCPs, and our AI 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 {dash} m, implement Code Rabbit suggested fixes. And run git 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. So, I'll remove the changes, mark it as resolved, committed, and we should be able to merge it. Wonderful. 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 PostHog link down in the description, and let me show you how to turn this from just a project into a successful product with PostHog. Click Get Started, choose your region, and register. For the name, I'll go with something like Adrian at JS 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, npx-y @posthog/wizard@latest. Then, within our application, open up the terminal and paste this command. Now, just before you run it, if you have Claude Code or any other AI coding tool running in your terminal, log out of it first because the PostHog 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 code base and sets up PostHog 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 PostHog wizard. There we go, login complete. Now, we can return to our terminal, and the wizard, now authenticated with the PostHog 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. 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 PostHog 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 PostHog file. Finally, it'll update the layout, insert the event tracking code into auth, and wrap everything up. And at the bottom part, we can see what the PostHog 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're literally plug it in, it figures everything out for you, go 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 PostHog MCP server. This lets your AI coding tools query your PostHog 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 PostHog. By we, I mean the hog itself. We can check the PostHog 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 sign-in attempt failed. The user signed up, when they actually complete the email verification and account creation with Clerk. User sign-up failed. User signed out, when they sign out from the setting 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 detail 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 PostHog React Native from app off sign in TSX. So, the first thing that makes sense is to actually install this package, PostHog React Native, within our second terminal by running NPM install or MPX Expo install PostHog React Native. There we go, it installed it. So now we can rerun our application with MPX Expo start with a dash C flag. So if you get a module resolution error after the wizard, it often times means that the package didn't install during the wizard step. So just install it manually and the 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 PostHog 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 PostHog, 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 PostHog 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 their 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 quick start, 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 PostHog 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 setting screen right here. So, what would happen if I went over to the setting screen and then reloaded, you can see that we touched a specific text or click the 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 PostHog 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 PostHog for. And it's not just for mobile applications, I actually prefer using it on all of 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. 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 home page. 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 code base, 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. It 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 home page 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 home page. Great job. 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 auth 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 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 code base, paying close attention to the existing design system, the constants, the icons, and the home screen. Then, create a new create subscription modal page that is a React Native modal 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. Zustand is available. So, we'll create a simple Zustand store for subscriptions so that it displays unified subscriptions both on the subscription screen and on the home page. 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 model subscription that accepts a couple of props. Is it visible? What happens when we close it? What happens when we 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 1 month or 1 year. Then, we form this new subscription by adding it the ID, passing in the name, the value, the currency, the frequency, the category, the status, as well as the start and renewal date, as well as the 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 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 on submit 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. Pressable is basically a button in this case. And this pressable 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 git add dot, git commit {dash} m, implement the subscriptions screen and the subscription create model, 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 PostHog 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 PostHog. So, simply say PostHog, which is going to automatically import it at the top from source config PostHog. This is your specific PostHog instance, and then say dot capture, which is one of the methods available on this PostHog 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 PostHog's dashboard under activity, you'll be able to see that this user right here created a subscription. And if you hide all the additional PostHog 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 a 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 PostHog. 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 or 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 through 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 PostHog. So yeah, we basically used that PostHog 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 PostHog 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 PostHog for any kind of website that I create. So, let's also commit that PostHog change by running get add {dot} get commit {dash}m implement PostHog tracking for subscription created. And run get push. Perfect. Now we have a bit of a better idea of how users are using our app. And back on Code Rabbit 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 store back subscriptions, which is true because we added Netflix, but the Netflix isn't going to show up here. So that's a little 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 PostHog 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 Juni 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 params sanitization in our layout to prevent sensitive data leakage, added PostHog tracking to handle verified path in sign in for complete coverage, replaced upcoming subscriptions static data with the 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 subscriptions static data with dynamic store data in index. Moved PostHog 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 dash m implement CodeRabbit 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 auto fix, 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. 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 npm 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 auth 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 D-U-N-S number and the organization name becomes the seller name. Once you enroll, you'll get access to the App Store Connect, TestFlight, 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 Expo 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 a 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 app.config.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 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 ReCurley, I will just change this over to ReCurley with a capital R. And the slug can be set to ReCurley like this. The version can be set to 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.yourname.recurly like this. Here you can put your personal name like Adrian. And we'll do the same thing for Android under Android. dot package and that's going to be com.adrian.recurly. These identifiers matter because they're 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 error 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 NPMCI can only install packages when your package and package-lock are in sync. So, we have to update our lock file with NPM install before continuing. So, what we can do is run rm -rf node_modules and package-lock.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 npm install to reinstall all the dependencies. There we go. And now that we updated it, we can also commit this new lock file to GitHub by running git add package-lock.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 keystore, 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 fastlane 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 submit {dash} {dash} 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 building 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 your 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 TestFlight. 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? Ha. 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 test flight 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 test flight application. So, yeah. As soon as you add yourself, you'll actually be able to test your app within test flight 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. 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'll learn how React Native works under the hood and why Expo is the standard. You then built a file-based routing system with the route groups, tab navigation, dynamic routes, and shared layouts. You styled the entire app with NativeWind, custom themes, and a design system that maps directly from Figma. You then loaded custom fonts and wired 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 PostHog 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 home page, 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 a 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 a real data persistence, server-side logic, and user-specific data, you need
Original Description
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.
Clerk: https://jsm.dev/nativesub-clerk
PostHog: https://jsm.dev/nativesub-posthog
Expo: https://jsm.dev/nativesub-expo
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/javascriptm
Watch on YouTube ↗
(saves to browser)
Sign in to unlock AI tutor explanation · ⚡30
Playlist
Uploads from JavaScript Mastery · JavaScript Mastery · 0 of 60
← Previous
Next →
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Learn Async/Await in This Real World Project
JavaScript Mastery
JavaScript Exercise | Learn JavaScript with Exercism | #0 Setup
JavaScript Mastery
JavaScript ES6 for Beginners
JavaScript Mastery
ES7 and ES8 New Features
JavaScript Mastery
Learn JSON in a Real World React App
JavaScript Mastery
How to Create PDFs With Node JS and React
JavaScript Mastery
Must Have Visual Studio Code Extensions
JavaScript Mastery
Top 10 JavaScript Array Methods
JavaScript Mastery
JavaScript Map and Set Explained
JavaScript Mastery
Git Commands Tutorial for Beginners
JavaScript Mastery
Build and Deploy a YouTube Clone Application Using React
JavaScript Mastery
React Hooks - Most Used Features
JavaScript Mastery
JavaScript Best Practices and Coding Conventions - Write Clean Code
JavaScript Mastery
Build and Deploy a Realtime Chat Application - Socket.io, Node.js, and React.js
JavaScript Mastery
How to Create and Deploy a Portfolio Site in less than 30 Minutes
JavaScript Mastery
SEO for Developers | 2020 SEO Tutorial
JavaScript Mastery
Web Development Roadmap 2020 [Learning Path] - Start Coding at Home!
JavaScript Mastery
Random Quote Generator - React Fetch API Data | Build and Deploy a Real Advice App Project
JavaScript Mastery
Build a COVID-19 Tracker Application - React JS Project (Hooks, Material UI, Charts js)
JavaScript Mastery
JavaScript ES2020 - The Most Requested Feature Explained in 10 Minutes
JavaScript Mastery
Modern React Event Handling Using Hooks
JavaScript Mastery
Deno JS - Intro + Real Life Example
JavaScript Mastery
Build and Deploy a React PWA - Why Progressive Web Apps are the Future of the Web
JavaScript Mastery
Build a REST API with Node JS and Express | CRUD API Tutorial
JavaScript Mastery
Build and Deploy an ARTIFICIAL INTELLIGENCE React App | Alan AI, JavaScript
JavaScript Mastery
Master Async JavaScript using Async/Await | Quokka JS
JavaScript Mastery
Spaced Repetition in Programming | mem.dev
JavaScript Mastery
Stop Copy & Pasting Code | mem.dev
JavaScript Mastery
GitHub Profile README | Create an Amazing Profile Readme | Setup + Templates
JavaScript Mastery
NEW GitHub CLI 1.0 is here! | GitHub CLI Tutorial - Demo & Commands
JavaScript Mastery
React Custom Hooks | Learn Custom Hooks & Build a Project
JavaScript Mastery
Learn how to deploy an NPM Package
JavaScript Mastery
JavaScript Algorithms for Beginners
JavaScript Mastery
Level UP your GitHub Game - Get Hired Quickly
JavaScript Mastery
The Best Way to Host & Deploy a React Application
JavaScript Mastery
Full Stack MERN Project - Build and Deploy an App | React + Redux, Node, Express, MongoDB [Part 1/2]
JavaScript Mastery
Full Stack MERN Project - Build and Deploy an App | React + Redux, Node, Express, MongoDB [Part 2/2]
JavaScript Mastery
ECommerce Web Shop - Build & Deploy an Amazing App | React.js, Commerce.js, Stripe
JavaScript Mastery
JavaScript Crash Course 2021 - Master JavaScript in One Video!
JavaScript Mastery
MERN Auth - Login with Email (JWT) + Google OAuth Authentication | React, Node, Express, MongoDB
JavaScript Mastery
Chat Application using React JS - Build and Deploy a Chat App in 1 Hour (Microsoft Teams)
JavaScript Mastery
MUST USE Websites & Tools for Web Developers
JavaScript Mastery
Learn Material UI in One Hour - React Material UI Project Tutorial [2022]
JavaScript Mastery
Shopify ECommerce Store with React & Next JS | BuilderIO
JavaScript Mastery
React Video Chat App | WebRTC Video Chat Zoom Clone | Tabnine
JavaScript Mastery
TypeScript Crash Course 2021
JavaScript Mastery
Build and Deploy a Premium Next JS React Website | Landing Page, Business Website, Portfolio
JavaScript Mastery
Full Stack MERN Project - Pagination & Search | React + Redux, Node, Express, MongoDB
JavaScript Mastery
Build a BETTER Facebook Messenger Chat Application | React JS, Firebase, Chat Engine
JavaScript Mastery
Build and Deploy THE PERFECT Portfolio Website | Create a Portfolio from Scratch
JavaScript Mastery
Full Stack MERN Project - Implement MERN Comments | React + Redux, Node, Express, MongoDB
JavaScript Mastery
Turn an API into a Startup?! Build & Sell an API with JavaScript
JavaScript Mastery
Exclusive First Look at GitHub Copilot - All you need to know
JavaScript Mastery
Build and Deploy a Google Maps Travel Companion Application | React.js
JavaScript Mastery
Build and Deploy a Full Stack Realtime Chat Messaging App with Authentication & SMS Notifications
JavaScript Mastery
Build and Deploy a React Cryptocurrency App and Master Redux Toolkit in One Video
JavaScript Mastery
Build and Deploy a Group Video Chat Application with Messaging, Polls & More
JavaScript Mastery
Build and Deploy Google Search 2.0 with React & Tailwind CSS (simple!)
JavaScript Mastery
Top 10 Web Development Chrome Extensions You Simply Need to Try!
JavaScript Mastery
Build and Deploy THE BEST Modern Blog App with React | GraphQL, NextJS, Tailwind CSS
JavaScript Mastery
More on: React
View skill →Related Reads
📰
📰
📰
📰
Inside the Wayfair Frontend SDE-2 Interview: A Complete Breakdown
Medium · Programming
I Spent Two Years Maintaining a React SPA. HTMX Rebuilt It in a Week
Medium · Programming
The 5 Levels of Front End Engineering (And Where Most Developers Get Stuck)
Medium · Programming
Browser-Based PDF Editing with Vue 3 and pdf-lib
Dev.to · sunshey
🎓
Tutor Explanation
DeepCamp AI