Introduction
The world is constantly changing, and the tech world, even more so. In recent years, we’ve seen a clear shift: more and more people are using their phones instead of computers to solve everyday problems. Naturally, mobile app development is growing along with that demand.
If you’re a web developer, chances are you’ve already gone through, or are about to go through, a common transition: moving from React in the browser to React Native on mobile. That’s exactly what happened to me, and probably to many other devs out there.
As developers, we tend to welcome new projects with excitement. But let’s be honest: the journey gets a lot smoother when we know what lies ahead.
This article isn’t a complete React Native manual, far from it. What you’ll find here is a study guide: a collection of practical insights, key differences, and helpful tips I wish I had known when I moved from React for the web to React Native.
If you’re currently making this transition, or even just considering it, I hope this helps you get your bearings and gives you a solid starting point for further learning.
React vs. React Native: similar, but not the same
React Native is a framework developed by Facebook that allows you to build iOS and Android apps using JavaScript (or TypeScript) with a shared codebase.
But there’s a key difference from React for the web: while React runs inside the browser and renders to HTML, React Native doesn’t use the DOM at all. Instead, it uses a bridge to communicate between your JavaScript code and the platform’s native UI components.
Here’s how it works: your business logic and layout definitions are written in JavaScript, and at runtime, React Native translates those components into their native equivalents, like a Text component becoming a UITextView on iOS or a TextView on Android. The JavaScript thread and the native thread communicate asynchronously via this bridge.
This doesn’t mean your JavaScript is compiled directly into Swift or Kotlin — it still runs in a JavaScript engine (like Hermes or JSC), but React Native handles the rendering natively instead of using HTML and CSS.
If you already use hooks, functional components, and good state management practices in React, great. That knowledge carries over smoothly. Core React concepts can be used on React Native, like:
- hooks: useState, useEffect, useContext and useReducer;
- custom hooks;
- functional components;
- lifting state up;
- component composition.
Even many third-party libraries you’re used to on the web still work, for example:
- state management tools like Redux;
- API clients like Axios or TanStack Query;
- form libraries like React Hook Form;
- validation with libraries like Yup.
However, some web-specific concepts don’t apply or need replacements in React Native:
- no HTML elements, like div, button, or input. Instead, you use components like View, TouchableOpacity, TextInput;
- no CSS files or class names, styling is done with inline JavaScript objects or libraries like styled-components/native;
- no DOM manipulation (e.g., document.querySelector, window, localStorage), you’ll need platform-specific alternatives;
- no browser-based navigation tools like React Router, you’ll use libraries like React Navigation.
You’ll start noticing these differences as soon as you begin rendering components or styling layouts. While the React mindset remains the same, the environment and tools change, sometimes quite a lot.
1. Moving past HTML: React Native’s unique component set
As we mentioned on section above, you’ll quickly notice that there is no HTML in React Native. This is because it doesn’t run inside a browser and doesn’t use the DOM. Instead, it provides its own set of primitive components that correspond to native UI elements on each platform.
For example, what would be a div on the web is replaced by a View component in React Native, which acts as a generic container for layout purposes. Similarly, span or p tags, which are used for text on the web, become Text components in React Native, responsible for rendering all textual content.
Images displayed with img in the web become Image components in React Native. Clickable elements like button are replaced by components such as TouchableOpacity, Pressable, or even a simple Button, all designed for touch interactions on mobile devices. Text input fields are replaced by TextInput components.
2. Styling tips (responsiveness is key)
Another important difference is styling. Unlike the web where CSS classes and external stylesheets are used, React Native uses JavaScript objects for styling, often created with the StyleSheet or libraries like styled-components/native. This means your layout and style logic are much more tightly integrated with your components, which can feel different at first but quickly becomes natural.
In summary, while the structure and logic of your UI still follow React principles, the fundamental building blocks you use to compose the interface in mobile apps are completely different from HTML elements on the web.
Have you noticed how many different screen sizes exist in the smartphone world? I couldn’t give you the exact number, but I can guarantee it’s a lot. And new ones are released all the time.
That’s why, just like in web development, responsiveness matters. Luckily, we have familiar tools at our disposal: Flexbox is widely used and fully supported.
Relative units will help you a lot, such as:
- em and rem: relative to font size;
- %: relative to parent size.
Yes, pixels are still valid, but use them wisely, especially in widths or heights that should adapt to the screen size.
Now, if you’re a CSS Grid fan, I’ve got bad news: Grid isn’t supported in React Native. Not yet, at least. So for now, Flexbox is your best friend.
3. API handling: good news here
Here’s some relief: if you’re already familiar with libraries like Axios and TanStack Query, you can continue using them in React Native without major changes.
The reason API handling works similarly in React Native and React is because both environments run JavaScript and support asynchronous operations with promises, async/await, and fetch APIs. React Native uses JavaScript engines like Hermes or JavaScriptCore to execute your code, which means the logic for making network requests, handling responses, and managing asynchronous data fetching behaves almost identically to the web.
This means that all your knowledge of a lot of things will apply for mobile, like:
- making API calls using fetch or Axios;
- managing remote data with tools like TanStack Query (including caching, background updates, and stale data handling);
- writing custom hooks to encapsulate API logic;
- handling loading and error states in your components;
The main differences you might encounter are platform-specific nuances, like network connectivity issues or permission handling on mobile devices, but these rarely affect how you write your API calls.
So, when it comes to fetching and managing data from APIs, React Native lets you reuse much of your existing React web expertise and tooling, making the transition smoother in this area.
4. Page navigation: forget React Router
One of the first surprises when moving from React web to React Native is that React Router, the go-to library for web navigation, is not the best choice for mobile apps. Instead, the most widely recommended and used library for navigation in React Native is React Navigation.
React Navigation is built specifically for mobile environments and embraces the native navigation patterns you expect on iOS and Android devices. This includes:
- Stack navigation: navigating forward and backward between screens, similar to a call stack, which is common in mobile apps.
- Tab navigation: bottom or top tabs for switching between major sections of an app.
- Drawer navigation: a slide-out menu from the side, often used for secondary navigation.
Unlike React Router, which primarily manages URL-based navigation suited for browsers, React Navigation manages navigation state internally and integrates tightly with native components to provide high performance and a seamless user experience.
Learning to use React Navigation involves understanding how to:
- define navigators and nested navigators to organize your screens;
- pass parameters between screens;
- customize header bars and transition animations;
- handle deep linking and integration with device hardware buttons (like Android’s back button).
At first, it might feel different and a bit complex if you’re used to React Router, but investing time in mastering React Navigation will pay off by making your mobile app navigation feel smooth, intuitive, and native.
5. Debugging in React Native: it’s a different experience
Debugging on the web is usually straightforward, you open your browser’s DevTools and start inspecting. With React Native, the process requires a bit more setup and different tools, but it becomes quite manageable once you’re familiar with the environment.
A key tool for debugging React Native applications today is React Native DevTools. It’s a modern debugging interface built into the React Native ecosystem, offering a range of powerful features, including:
- A JavaScript console for logging and inspecting your code;
- Native support for Flipper, an extensible debugging platform maintained by Meta. Flipper allows you to inspect your app’s state, monitor network requests, view logs, and even analyze the layout hierarchy in real time.
- Tools for monitoring asynchronous behavior and the usage of React hooks.
React Native DevTools works well with both plain React Native projects and those built using Expo, especially with recent versions of the Expo SDK. For Expo apps, you can also use Expo DevTools, a web-based interface that supports fast prototyping, access to device logs, and various debugging utilities.
Keep in mind that setting up your development environment for debugging, especially on a physical device or emulator, requires some initial configuration. This includes enabling debug mode, connecting your device via USB or local network, and ensuring proper environment setup.
Once configured, these tools offer deep insights into your app’s behavior, helping you identify and resolve issues more efficiently and effectively.
6. Handling platform-specific behavior in React Native
Although React Native lets you write most of your code once and run it on both iOS and Android, not everything is 100% cross-platform. Different operating systems have unique features, behaviors, and limitations, so sometimes you need to customize your app for each platform.
Fortunately, React Native provides several tools to manage these platform-specific needs:
- Platform-specific file extensions: you can create separate component files for iOS and Android by using extensions like .ios.tsx and .android.tsx. React Native will automatically pick the correct file when building for each platform. This lets you tailor UI or logic where necessary without cluttering your code with conditionals.
- The Platform API: React Native exposes a simple Platform module that lets you detect the OS at runtime. For example, you can write conditional code using Platform.OS === ‘ios’ or Platform.OS === ‘android’ to execute platform-specific logic within the same file.
- Native module dependencies: in many situations where you need a library to handle a specific feature, like accessing the camera, geolocation, or background tasks, that library may include native code for both platforms (iOS and Android). This means you’ll often need to perform platform-specific installation steps. Be sure to follow each library’s documentation carefully, as setup instructions can vary significantly between platforms.
The golden rule: always test your app early and frequently on both iOS and Android devices or emulators. This helps you catch platform-specific issues before they become bigger problems.
By leveraging these strategies, you can deliver a polished experience tailored to each platform while still maximizing code reuse.
7. Testing in React Native: still essential, just a bit different
Testing your code in React Native is just as important as it is in web development, and in some ways, even more so. Mobile apps often need to support a wide range of devices, screen sizes, and operating systems. That means testing helps prevent unexpected issues and gives you confidence to release new features without fear of breaking things.
Fortunately, many of the testing tools you already use in React for the web can be used in React Native too, though with a few important differences.
For unit and integration tests, React Native uses Jest by default. You can continue testing pure functions, hooks, and isolated components in the same way you’re used to. When it comes to rendering and testing UI components, the best option is the React Native Testing Library, which works similarly to React Testing Library but is adapted for the mobile environment. Since React Native doesn’t use the browser DOM, the library relies on native component trees instead. That means some queries, like getByRole, may behave differently, but the overall testing approach, focusing on how users interact with your UI, stays the same.
For end-to-end testing, where the goal is to simulate real user behavior such as tapping buttons, navigating between screens, and filling out forms, a well-known solution in the React Native ecosystem is Detox. Detox runs your tests on real devices or emulators and ensures that the app behaves as expected from the user’s perspective. It supports both iOS and Android and integrates with Jest for structured test writing.
However, while Detox is powerful, it often comes with a steep setup cost. Developers frequently encounter issues related to native build configurations, dependency conflicts, and especially synchronization errors, where the test runner struggles to correctly wait for async tasks to finish, leading to flaky or unstable tests.
That’s where Maestro comes in as a modern, developer-friendly alternative. Maestro offers a simpler, more reliable approach to E2E testing:
- No need to build the app from scratch for each test run, you can test against an already installed app on the emulator or device;
- Declarative, YAML-based test definitions make test scripts easy to read and maintain;
- Cross-platform support with minimal setup and no need to manage native dependencies;
- Much fewer synchronization issues since Maestro doesn’t try to over-control async behavior.
In practice, many developers find Maestro to be simpler and faster to work with, especially when getting started or maintaining tests over time. While Detox may still be useful in highly customized setups or teams already invested in it, Maestro is quickly becoming the preferred tool for E2E testing in modern React Native projects.
Other tools and practices from the web testing world also apply. Snapshot testing, for example, is still available through Jest and can be useful for catching unexpected UI changes, though it’s best used sparingly. You’ll also need to handle asynchronous code carefully, especially when dealing with animations or delayed rendering. Utilities like waitFor, findBy, and act() help make tests more stable and predictable.
One thing to keep in mind is that testing mobile apps often involves additional setup compared to the web. Running tests on emulators or physical devices takes more time and resources, and some native APIs might require mocking or custom test configurations, especially when dealing with things like location, camera, or push notifications.
In summary, testing in React Native shares many familiar patterns from React on the web, but there are some important platform-specific challenges. The key is to start simple, test what matters most, and build a reliable suite over time. With the right tools and mindset, you’ll be able to maintain a stable, high-quality mobile app, and ship with much more confidence.
8. Local Storage: using AsyncStorage in React Native
In web development, storing data locally is often as simple as using localStorage. But in React Native, since the app doesn’t run in a browser, that option doesn’t exist. Instead, the most common and recommended way to persist local data is by using AsyncStorage.
AsyncStorage is a key-value storage system maintained by the React Native Community. It allows your app to store data directly on the device, making it ideal for saving things like user authentication tokens, theme preferences, onboarding flags, and other simple settings or lightweight state that you want to persist between sessions.
Unlike localStorage, AsyncStorage works asynchronously, meaning that reading and writing data happen through promises. This helps prevent UI blocking, but it also means you need to handle it carefully using async/await or .then() to avoid unexpected issues in your app logic.
AsyncStorage is a solid choice for simple and medium-scale storage needs, but it comes with an important caveat: AsyncStorage does not encrypt data at rest. This means any sensitive information stored using AsyncStorage is vulnerable to inspection if someone gains access to the device’s file system. For apps that require secure storage (e.g., storing access tokens, personal user data, or anything subject to compliance requirements), AsyncStorage is not recommended.
In such cases, consider using a more secure alternative like react-native-mmkv, which offers encrypted, high-performance, and synchronous storage backed by Facebook’s MMKV library. It’s a great drop-in replacement for AsyncStorage when performance and security are a priority.
AsyncStorage is a great fit for small- to medium-sized storage needs. However, it’s not designed for handling large datasets, complex structures, or relational data. For those use cases, developers usually look into more advanced solutions like SQLite or realm. Still, for the majority of mobile applications, especially those coming from a web background, AsyncStorage covers most needs.
It’s also important to note that since storage is asynchronous and device-based, you should always handle read/write operations within try/catch blocks and plan for potential failures, such as when the user clears app data or storage is full.
In short, if you’re transitioning from web development and wondering where to store local data in React Native, AsyncStorage is your closest equivalent to localStorage, and it’s widely used, well-documented, and easy to integrate into any project.
Conclusion: it’s still React, just in a different world
Making the jump from React for the web to React Native isn’t about starting over: it’s about learning how to apply what you already know in a new environment. The core concepts of React still apply: components, hooks, props, state, lifting state up, separation of concerns, those remain your foundation. But the tools, the building blocks, and the context around them change.
You’ll have to get used to new components, different styling practices, a different approach to navigation, and a mobile-specific way of thinking about layout, performance, and user experience. You’ll debug differently, store data differently, and sometimes you’ll have to account for the quirks of iOS and Android, things you never had to think about when working in the browser.
And yet, the learning curve is worth it. React Native opens the door to building powerful, native-feeling mobile apps using your existing JavaScript and React skills. You don’t have to reinvent yourself as a native developer, but you do have to adapt. That’s the key.
This article wasn’t meant to be an exhaustive guide, and it certainly doesn’t cover every edge case or advanced topic. Instead, it’s a starting point, a collection of insights, gotchas, and practical notes that I wish someone had told me when I made this transition. Hopefully, it gave you a clearer picture of what to expect and how to better navigate the shift.
Keep exploring, keep experimenting, and don’t be afraid to break things along the way. Just like with web development, the best way to learn React Native is to build with it. So take what you know, build something small, and grow from there. You’re already halfway there.
References
- https://reactnative.dev/docs/getting-started
- https://reactnative.dev/docs/components-and-apis
- https://reactnavigation.org/docs/getting-started
- https://reactnative.dev/docs/debugging
- https://reactnative.dev/docs/platform-specific-code
- https://jestjs.io/docs/tutorial-react-native
- https://testing-library.com/docs/react-native-testing-library/intro
- https://docs.maestro.dev/
- https://react-native-async-storage.github.io/async-storage/docs/install/
- https://github.com/mrousavy/react-native-mmkv
We want to work with you. Check out our Services page!