Meet React Native (Deep Dive)
Flutter is a wonderful way to build apps, but it is not the only one. The other giant in cross-platform development is React Native, created by Facebook (Meta) and used to build apps like Instagram, Discord and parts of Facebook itself. It is worth understanding, because it takes a genuinely different approach — and because it uses JavaScript, the world's most widely-known programming language.
A Different Philosophy from Flutter
Recall that Flutter is like a painter: it draws every pixel of every button itself, so your app looks identical on all devices. React Native works differently — it is more like a manager giving instructions. You write JavaScript, and React Native asks the phone's own operating system to create real, native iOS and Android components on your behalf.
Under the Hood: Threads & Architecture
To truly master React Native, you must understand its threading model. A React Native app runs on three main threads:
- The UI Thread (Main Thread): Handles drawing the native UI components (like
UIViewon iOS orViewGroupon Android). - The JavaScript Thread: Executes your React code, runs business logic, and determines what the UI should look like.
- The Background/Shadow Thread: Calculates the layout using a C++ engine called Yoga (which converts Flexbox rules to absolute native coordinates).
The Bridge vs. The New Architecture (JSI)
Historically, the JS thread communicated with the UI thread via a Bridge — converting instructions to JSON strings, sending them across asynchronously, and translating them back. While effective, this serialization caused performance bottlenecks during heavy animations or rapid scrolling.
Today, React Native fundamentally changed this with the New Architecture, built on three pillars:
- Fabric: The new rendering system that works synchronously, eliminating the "UI jumpiness" and allowing React to directly manipulate native views without the bridge overhead.
- TurboModules: Allows native modules (like camera or Bluetooth) to be loaded lazily only when needed, speeding up app startup times immensely.
- JSI (JavaScript Interface): Allows JavaScript to hold references to C++ native objects and call their methods directly without JSON serialization. This makes modern React Native incredibly fast, bringing it closer to native performance.
Alongside this, React Native now defaults to using Hermes, a lightweight JavaScript engine built by Meta specifically for running React Native on mobile. It pre-compiles your JS code into bytecode, meaning your app launches significantly faster and uses less memory.
JavaScript drives real native components
code
translates
iOS/Android
- You write your app in JavaScript — the world's most-known language.
- The JSI / Bridge translates your instructions synchronously or asynchronously…
- …and asks the phone to build real native buttons and views. Unlike Flutter, it uses the platform's own pieces.
Key Takeaways
- React Native is the other big cross-platform tool, made by Meta, using JavaScript.
- Unlike Flutter (which paints pixels), it asks the phone to create real native components.
- It operates on three main threads: UI, JS, and Background layout (Yoga).
- The New Architecture (JSI, Fabric, TurboModules) removes the asynchronous bridge, offering near-native performance.
Your Turn: Practice
List three apps you have heard are built with React Native (search if needed). Then describe in your own words the difference between the old Bridge architecture and the New Architecture (JSI).
Advanced Components & JSX
Where Flutter has "widgets," React Native has components. The idea is beautifully similar: your interface is built from small, reusable pieces that you combine into bigger ones. Let's dive deeper into component patterns.
A Component is Just a Function
In modern React Native, a component is a JavaScript function that returns some UI. Here is a tiny one that displays a greeting:
That component can now be reused anywhere, like a custom building block: <Greeting />.
JSX Deep Dive & Conditional Rendering
JSX lets you describe your UI using HTML-like tags inside JavaScript. Because it is JavaScript, you can seamlessly embed logic using curly braces {}.
One of the most powerful features of JSX is Conditional Rendering. Instead of writing complex if-else statements, you can use the logical AND operator (&&) or the ternary operator (? :):
The Golden Rule of JSX & Fragments
A component in React Native must return exactly one top-level element. If you don't want to use a wrapper <View> that affects layout (like Flutter's Column or Row), you use a Fragment (<>...</>):
Props Destructuring & Default Values
Props (properties) pass data down to components. Modern JS developers use destructuring and can even set default values directly in the function signature:
Composition with children
React Native uses a special prop called children for component composition. It allows you to create wrapper components (like a Card) without knowing what will go inside them beforehand:
Custom Hooks: Reusing Stateful Logic
When you have logic that you want to share across multiple components (like tracking the device's online status or a countdown timer), you don't copy-paste the state. Instead, you extract it into a Custom Hook.
The same idea, everywhere
Notice how this mirrors Flutter almost exactly: reusable pieces (widgets / components) that accept inputs (constructor arguments / props) to customise them. This "build your UI from small reusable pieces" philosophy is the dominant approach in modern app development.
Composing a UI from components
- Everything lives inside one top-level component:
<App />. - It renders a reusable
<Navbar />component. - …and a
<Feed />component. Nest components to build any interface.
Key Takeaways
- React Native's building blocks are components (functions that return JSX).
- Conditional rendering with
&&and? :is a powerful alternative to if/else. - Destructuring props with default values makes code significantly cleaner.
- The
childrenprop enables flexible component composition, like Flutter's `child` or `children`.
Your Turn: Practice
Write a React Native component named WarningBanner that takes two props: message and isVisible. Use conditional rendering to return a <Text> component containing the message only if isVisible is true, otherwise return null.
Advanced Core Components
React Native gives you a set of fundamental components to build any screen. Let's look at them in depth, including advanced components for handling keyboards, complex lists, and interactive elements.
View & SafeAreaView
A View is the basic building block (like Flutter's Container or a web div). Almost everything lives inside a View.
Modern phones have notches, dynamic islands, and home indicator bars. SafeAreaView is a special View that ensures your content is rendered within the safe area boundaries of an iOS device, preventing text from being hidden behind hardware elements.
Handling Keyboards: KeyboardAvoidingView
On mobile, when the user taps a text input, the virtual keyboard slides up. By default, it will cover your UI. React Native provides KeyboardAvoidingView to automatically move your inputs out of the way.
Advanced Lists: FlatList & SectionList
For long lists, FlatList is essential because it is "lazy" — it only renders the items currently visible on the screen, recycling off-screen views. For lists with headers (like a contact list sorted by alphabet), use SectionList:
Pull-to-Refresh with RefreshControl
You can easily add "pull-to-refresh" functionality to any ScrollView, FlatList, or SectionList by using the refreshControl prop:
Interaction: Pressable vs TouchableOpacity
While TouchableOpacity is older and provides a simple fade effect, modern React Native recommends Pressable. Pressable gives you fine-grained control over various press states (pressed in, hovered, focused) allowing for complex interactions.
| Flutter (Dart) | React Native (JS) |
|---|---|
| Container / SafeArea | View / SafeAreaView |
| ListView.builder | FlatList |
| RefreshIndicator | RefreshControl |
| GestureDetector / InkWell | Pressable / TouchableOpacity |
React Native's core building blocks
<View>is the container — like Flutter's Container or a web div.<Pressable>detects complex touch and hold gestures.<KeyboardAvoidingView>prevents the keyboard from hiding your inputs.<SectionList>renders long grouped lists with sticky headers efficiently.
Key Takeaways
KeyboardAvoidingViewis essential for forms on mobile devices.SectionListhandles lists with categorized headers, whileFlatListis for flat arrays.RefreshControleasily adds pull-to-refresh to any scrolling component.Pressableis the modern, powerful alternative toTouchableOpacity.
Your Turn: Practice
For each React Native component in this lesson, write down its Flutter equivalent from memory. If you can map all of them, you have proven the two frameworks share one mental model.
Advanced Styling & Hooks (State)
Now let us make React Native components look good and respond to the user. We will explore Flexbox layout mastery and advanced React Hooks.
Styling: StyleSheet & Absolute Positioning
While you can pass inline styles, using StyleSheet.create provides performance benefits (styles are created once, not every render) and autocompletion.
Unlike the web, React Native defaults to display: 'flex' and flexDirection: 'column'. For overlapping UI (like a floating action button), you use absolute positioning:
Advanced State: useReducer
When state logic gets complex (e.g., managing a shopping cart with ADD, REMOVE, CLEAR actions), useState can become messy. React offers useReducer, which works similarly to Redux, keeping your state transitions predictable.
Side Effects & Cleanup: useEffect
The useEffect Hook runs code automatically after the component renders. A crucial concept is the cleanup function. If your effect sets up a subscription or a timer, you must clean it up to prevent memory leaks when the component unmounts.
Performance: useMemo and useCallback
In React, whenever state changes, the component re-renders. If you have a heavy calculation or pass functions to child components, they get recreated every render.
useMemo caches the result of a calculation. useCallback caches a function definition.
Platform-Specific Code: Platform.select
Sometimes you need different behavior or styling depending on whether the app is running on iOS or Android. React Native provides the Platform API to handle this cleanly without messy if-else chains.
One mental model, two toolkits
By now you should feel it clearly: components and widgets, useState and setState, Flexbox and Row/Column, useEffect return function and dispose() — these are the same handful of ideas appearing in both frameworks.
useReducer for complex state
- User taps ADD. An action is dispatched to the
reducerfunction. - User taps RESET. The reducer handles complex logic predictably. State changes → component re-renders.
Key Takeaways
- Use
StyleSheet.createfor performant styling. React Native relies heavily on Flexbox and absolute positioning. useReduceris excellent for managing complex, multi-action state.- Always return a cleanup function in
useEffectif you start a timer, listener, or subscription. - Use
useMemoanduseCallbackto optimize performance and prevent unnecessary re-renders.
Your Turn: Practice
Recall the Flutter `dispose()` method. Now describe, in words, how the same cleanup logic is implemented in React Native using useEffect.
Data Fetching & State Management
Fetching data is straightforward with fetch, but managing that data across a large app requires robust strategies. We will look at standard data fetching and modern caching tools.
The fetch API with Hooks
To fetch data when a screen opens and display it on the UI, you combine useState (to store data, loading, and error states) and useEffect.
React Query (TanStack Query)
Writing the above boilerplate for every screen is exhausting. Modern React Native apps use libraries like React Query. It automatically handles caching, background refetching, retries, and pagination with a single Hook!
Global State Management
For data that isn't from an API (like UI themes, active modals, or simple user preferences), you can use the Context API. For very complex local state, the industry standard is moving towards Zustand (a simpler alternative to Redux).
The Power of the Web Ecosystem
Because React Native uses standard JavaScript, you are not limited to mobile-only libraries. Tools like Axios, React Query, Zustand, and Redux work perfectly on both web and mobile, allowing maximum code reuse.
The Data Fetching Lifecycle
- Component mounts,
useEffecttriggers a fetch request. - While waiting,
loadingstate is true. The UI shows a spinner. - Data arrives!
setUseris called,loadingbecomes false. UI updates automatically.
Key Takeaways
- Data fetching requires tracking three states: loading, data, and error.
try...catch...finallyguarantees loading state is cleared regardless of success or failure.- React Query is the modern industry standard for remote data, replacing Redux for API caching.
- The Context API or Zustand are used for global, non-API state.
Your Turn: Practice
Write a useEffect block that fetches data from https://api.github.com/users/octocat, handles loading and errors, and stores the user data in state.
Native Device Integrations
To build a truly mobile experience, you must access device hardware: the camera, GPS, local storage, and push notifications. React Native excels at this, particularly when using Expo.
Expo SDK: The Ultimate Toolkit
If you are using Expo, you have access to a massive library of pre-built, highly optimized native modules. You simply install them and call Javascript functions.
1. Local Storage: AsyncStorage / SecureStore
To keep a user logged in, you store their authentication token on the device. SecureStore encrypts it natively.
2. Camera & Image Picker
Allowing users to pick an avatar is a breeze with Expo Image Picker.
Permissions are Mandatory
Mobile operating systems strictly protect user privacy. Before you can access the camera, location, or notifications, you must request permission. Modern libraries handle the native permission dialogs for you.
What are Native Modules?
Under the hood, JavaScript cannot directly talk to an iPhone's Bluetooth chip. A Native Module is a bridge consisting of native code (Swift/Objective-C or Kotlin/Java) that performs the hardware task, and a JavaScript interface.
With the JSI (JavaScript Interface) in the New Architecture, JavaScript can directly invoke native methods synchronously, making features like complex camera filters or real-time audio processing incredibly fast.
Background Tasks & TaskManager
Running code when the app is closed or in the background (like location tracking or fetching new messages) requires special handling. Expo provides TaskManager to register background functions that the native OS can wake up and execute.
Building Your Own Native Modules
What if a company gives you a proprietary Android SDK, but no React Native package exists? You can write a custom Native Module yourself using Swift or Kotlin, wrapping their SDK and exposing a Javascript function. This is React Native's superpower: you are never stuck.
Key Takeaways
- The Expo SDK provides ready-to-use modules for almost all device features.
- Always use
SecureStore(or encrypted storage) for sensitive data like auth tokens. - You must request and verify permissions before accessing privacy-sensitive hardware.
- Native Modules connect JavaScript to Swift/Kotlin under the hood.
Your Turn: Practice
Name two device features you would need to ask permission for, and two that you wouldn't (e.g. AsyncStorage does not require permission, Camera does).
Flutter vs React Native: The Verdict
You now understand both of the world's leading cross-platform frameworks in depth. A natural question follows: which one should you actually build with? Both are excellent, widely used by top-tier companies, and will serve your career well. But here is the technical comparison.
Architectural Differences
| Feature | Flutter | React Native |
|---|---|---|
| Core Language | Dart (Strongly Typed, OOP) | JavaScript / TypeScript (Dynamic) |
| Rendering Engine | Impeller / Skia (Paints every pixel directly to canvas) | Fabric (Instructs OS to render Native UI views) |
| UI Consistency | Looks identical on all devices | Adopts the native look of iOS or Android automatically |
| Performance | 60-120fps out of the box, highly optimized rendering | Near-native (especially with JSI & Hermes) |
| Ecosystem | pub.dev (Highly curated for Flutter) | npm (Massive, but requires careful selection) |
Choose Flutter if...
- You want stunning, highly custom designs and complex animations with zero stutter.
- You prefer a strictly typed, object-oriented language (Dart) that feels like Java or C#.
- You want a cohesive ecosystem where navigation, material design, and animations are built into the core framework by Google.
- You are starting from absolute zero with no web background.
Choose React Native if...
- You already know JavaScript or TypeScript, or you also want to build websites.
- You want your app to look exactly like standard iOS and Android apps (using native OS components).
- Your team already uses React on the web and you want to share business logic, state management (Redux/Zustand), and data fetching (React Query) code.
- You want to leverage Over-the-Air (OTA) Updates via tools like Expo Application Services (EAS), allowing you to push bug fixes instantly without App Store review.
React Native CLI vs Expo
If you choose React Native, you must decide between React Native CLI or Expo. Expo is a framework wrapped around React Native (similar to Next.js for React). It hides the complex native iOS/Android folders, provides an immense suite of built-in libraries, and manages the build process in the cloud. Today, the official React Native documentation strongly recommends starting with Expo.
The ultimate truth of Software Engineering
Do not agonise over this choice. The fundamentals — logic, UI as components, state management, asynchronous programming, and clean architecture — are what truly matter. Frameworks are just tools; a skilled developer is not tied to a hammer or a wrench. Pick one, build something real, and launch it.
Choosing your framework
Dart · paints pixels
JS · native parts
- Flutter (Dart) paints every pixel itself — great for custom design and flawless animations.
- React Native (JavaScript) uses the phone's real native components — great if you want native feel or know JS.
- Both are excellent. Pick one, build something real, and stop comparing — the fundamentals transfer either way.
Key Takeaways
- Both Flutter and React Native are top-tier, widely-used frameworks.
- Flutter offers a highly unified ecosystem and identical custom UIs across devices.
- React Native offers unparalleled web-synergy, native UI components, and OTA updates.
- Fundamentals matter far more than the framework — mastering one makes the other trivial.
Your Turn: Practice
Make your decision: which framework will you focus on first, and why? Write three honest sentences based on your background and goals.
Animations & Advanced Performance
To make your app feel truly professional and "native", smooth animations and top-tier performance are non-negotiable. Let's explore the industry-standard tools for achieving 120fps animations and rendering massive lists seamlessly.
React Native Reanimated
The built-in Animated API in React Native is good, but Reanimated (by Software Mansion) is the industry standard for complex, gesture-driven animations. It runs animations directly on the UI thread, bypassing the JS bridge entirely.
FlashList: The Ultimate List
While FlatList is fine for simple arrays, it struggles when rendering hundreds of complex items (like a social media feed). Shopify created FlashList to solve this. It recycles views aggressively, offering 5x better performance than FlatList.
Hermes Engine
React Native now uses Hermes by default. It's an open-source JavaScript engine optimized for React Native. It compiles your JS into bytecode ahead of time (AOT), resulting in faster app launches, decreased memory usage, and smaller app sizes.
Bridgeless Mode & Fabric
With the New Architecture, React Native is moving towards a completely Bridgeless Mode. This means C++ and JS communicate synchronously without serializing JSON across a bridge. Combined with Reanimated and FlashList, React Native performance is virtually indistinguishable from purely native Swift/Kotlin code.
Key Takeaways
- Use Reanimated for smooth, UI-thread animations and gestures.
- Replace
FlatListwith Shopify's FlashList for long or complex lists. - Hermes is the default JS engine that pre-compiles code for faster startup.
Your Turn: Practice
Compare Animated.View in React Native with Flutter's AnimatedContainer. What is the fundamental difference in how they execute animations under the hood?
State Management Mastery
As your app grows, passing props down through many layers of components (prop drilling) becomes a nightmare. To solve this, we use global state management tools. Let's look at the industry standards: Redux Toolkit and Zustand.
The Modern Standard: Zustand
Zustand (German for "State") is a small, fast, and scalable barebones state-management solution. It's much simpler than Redux and has become the community favorite.
Redux Toolkit (RTK)
For massive enterprise applications, Redux is still the king. However, raw Redux requires a lot of boilerplate. Redux Toolkit (RTK) is the official, recommended way to write Redux logic.
Key Takeaways
- Stop prop drilling by using a global state manager.
- Zustand is the modern, lightweight favorite for most applications.
- Redux Toolkit (RTK) provides a highly structured environment for enterprise-scale apps.
Your Turn: Practice
Compare Zustand's syntax to Redux Toolkit. Notice how Zustand doesn't require a Provider wrapping your app. Which one feels more natural to you?
Testing React Native Apps
Professional apps must be tested before releasing to millions of users. In React Native, testing is broken down into Unit Testing, Component Testing, and End-to-End (E2E) Testing.
Unit & Component Testing: Jest & RNTL
Jest is the standard JavaScript testing framework. Combined with React Native Testing Library (RNTL), you can render your components in memory and interact with them.
End-to-End (E2E) Testing: Detox
While component tests run quickly in memory, they don't test the real native app. Detox is an E2E testing and automation framework that boots up a real iOS Simulator or Android Emulator, taps the screen like a real user, and asserts results.
Key Takeaways
- Jest is used for testing raw JavaScript functions (Unit Testing).
- React Native Testing Library is used for testing component rendering and user interactions without a real device.
- Detox is used for full End-to-End testing on real emulators/simulators.
Your Turn: Practice
Write a simple unit test outline for a `calculateDiscount` function using Jest's `expect` syntax.
Deployment & EAS (Expo Application Services)
Building the app is only half the battle. Releasing it to the Apple App Store and Google Play Store used to be a painful process involving certificates, Xcode, and Android Studio. Today, Expo has revolutionized this.
Expo Application Services (EAS)
EAS is a cloud service by Expo that handles building, submitting, and updating your apps. You do not even need a Mac to build an iOS app anymore!
1. EAS Build
Instead of building the binary (.apk/.aab for Android, .ipa for iOS) on your local machine, EAS compiles it in the cloud.
2. EAS Submit
Once built, you can upload the binary directly to the App Store Connect and Google Play Console from your terminal.
Over-The-Air (OTA) Updates: EAS Update
This is React Native's greatest superpower. If you find a bug in your JavaScript code, you do not need to wait 3 days for Apple to review your new app version. You can push an OTA update that users download instantly.
Note: OTA updates can only change JavaScript and assets. If you add a new Native Module (like a new camera SDK), you must do a full App Store release.
Continuous Integration / Continuous Deployment (CI/CD)
In a professional environment, developers set up GitHub Actions to run Jest tests on every commit, and automatically trigger an EAS Build when merging to the main branch.
Key Takeaways
- EAS Build compiles iOS and Android apps in the cloud, removing the need for local Xcode/Android Studio setups.
- EAS Submit uploads binaries directly to app stores.
- EAS Update allows you to push JavaScript bug fixes instantly over-the-air, bypassing app store reviews.
Your Turn: Practice
Explain the difference between a bug that can be fixed via EAS Update and a bug that requires a full EAS Build and App Store submission.