The Definitive Guide: What to Expect When Moving from React Web to React Native
One of the appeals of using React Native for building mobile applications is that it's underpinned by React — a UI library that might be considered the "gold standard" for building dynamic web interfaces. However, don't be fooled into thinking that since React Native apps and React-based web apps are both underpinned by React that you'll be able to casually share code between Native and Web, or that expertise in React Web will transfer directly over to React Native.
In this post, I'm going to discuss similarities and differences between React Native and React Web (web apps built on top of React). I'll argue that React Native and React Web are similar enough paradigms for an engineer to transition from React Web to React Native with a relatively low amount of friction, but that this transition should not be considered frictionless or trivial — and that the differences between React Native and React Web are significant enough that if you're an engineer transitioning from React Web to React Native, you should expect to shift your thinking about architectural and UI design accordingly.
Let's walk through some key differences between using React Native to build cross-platform mobile applications and using React Web to build web applications.
First and foremost, React Web renders to HTML and runs in a web browser, whereas React Native renders to native mobile elements (such as
UIViews on iOS and
ViewGroups on Android). Therefore, the primitive elements you use to construct your UIs vary drastically between React Web and React Native. In the context of React Web, you might reason in terms of
<span> HTML elements, whereas in React Native, you must reason in terms of
<Text> elements that are just cross-platform wrappers around native iOS and Android UI elements. Therefore the semantics and constraints of HTML that web-focused engineers are accustomed to do not directly apply to creating UIs in React Native — and in fact, there's a new set of semantics and constraints to understand to appropriately harness the power of React Native's abstractions.
Beyond just render targets, React Web and React Native execute in fundamentally different environments: React Web executes in a web browser, and therefore has access to browser APIs, whereas React Native executes inside of a JS runtime from inside of a native mobile application (and therefore does not have access to browser APIs). In the web context, you have access to things like the DOM or
localStorage. In the native context, these things do not exist. React Native (and third-party libraries) provide some JS APIs for accessing native-level APIs, but browser APIs that web engineers might be used to using generally do not exist in native-land.
This difference in execution environments means that engineers must shift their thinking about environment-provided APIs when writing their own code. Many React libraries (or JS libraries in general) rely on the fact that they are being executed in a browser environment and rely on browser APIs. These libraries will therefore not work in a React Native application (e.g., if a library uses DOM APIs, you should expect some sort of crash or error, if you're lucky, since there is no DOM — and hence no DOM APIs — in a React Native application).
The difference in underlying platforms means that a move from React Web to React Native entails saying goodbye to the browser-provided tools that you know and love, and embracing a new set of APIs and constraints to use with React in the native context — such as haptic feedback, accelerometer, AR/VR, push notifications, and much more!
Different Styling Mechanisms
Styling is a significant part of UI development in the context of both the web and native mobile. React Web applications fundamentally use CSS for their styling (even if there's an abstraction layer on top of it). However, CSS is a browser technology and does not target native mobile applications. Therefore, React Native applications do not use CSS for styling.
React Native provides a styling API, where style declarations on specific elements get transformed into native styling calls. This API is object-based, does not "cascade" styles like CSS does, and is much more limited in comparison to CSS. Since there is no DOM in React Native, there is also no notion of CSS selectors based on any sort of DOM-like structure, which entails that styles must be explicitly applied to individual elements — which looks a lot like writing inline-styles in CSS (with the ability to do some JS-level abstracting).
This drastic difference in styling tools can be a bit jarring at first. CSS experts might feel like they're building a house with no hands when attempting to do styling in React Native due to the lack of selectors, cascading, and extended styling tools that CSS provides. However, the limited styling API in React Native arguably makes it easier to learn and master — and often requires you to use a little more JS to aid in styling as opposed to CSS (which might suit your fancy, depending on your appreciation of CSS).
Different Navigation Paradigms
The URL serves a lot of key purposes on the web. It's essential for requesting resources from a web server, such as the HTML, JS, and CSS assets required to render a web application. It also generally serves as a canonical representation of the user's navigational state within a web application — e.g.,
https://mysite.com/profiles/tom might represent a navigation state on
mysite.com where the user is visiting some user's profile (in this case, Tom's).
In React Native, URLs play a significantly different role. They are still used to request resources from the web (such as images, data blobs, etc.) — but they are not used to request the core assets needed to run the application, nor are they used as a representation of a user's navigational state within the application. In fact, there aren't any built-in navigational structures in React Native that emulate a web URL. Instead, React Native applications generally use a third-party navigation library such as React Navigation to handle navigation, and generally navigational state is not persisted between sessions of app usage like you would expect when refreshing a web page.
Whereas web apps' navigational structures are often thought of as "pages" to be navigated between, mobile apps' navigational structures are often thought of in terms of mobile navigational primitives such as the "stack navigator." In the context of mobile navigation, a "stack" is stack of screens — where you can push a screen onto the stack (say, if you want to navigate to a product's detail screen), and pop the latest screen off the top if you want to go back (say, by hitting a back button, or swiping back). Here's an example of a stack navigator in action on the Amazon mobile app. Think of this like a stack of cards, where when you want to view something new, you place a new card on the top of the stack — and when you want to go back, you take the top card off of the stack.
This difference in navigational paradigms means that you must think differently about how you structure your application's "screens" from both a UI and architectural perspective, as the way users interact with and navigate between them varies from web to native.
Different Gesture and Animation Tools
The mouse and keyboard are the classic hardware devices for interacting with a web application. For mobile applications, we assume that the user's fingers are the only pointer devices they use to interact with with the application. This means that the mental model of UI interaction is significantly different between these two contexts: on the web, you might be used to thinking in terms of DOM events like
mouseup, whereas on mobile you should get used to thinking in terms of finger gesture events like panning, tapping, and pinching.
As UI developers we use animation to accompany gestures so that we can provide feedback to the user that their gestures are being recognized and acted upon accordingly — along with alerting the user that some sort of application state has changed. However, the animation tools available in React Web and React Native are quite different. On the web a large portion of animations can be driven by CSS keyframes and/or animation frames with direct DOM manipulation. However, these tools aren't available in React Native.
In React Native, we have to worry about how many times we "cross the bridge" between the JS thread and the native UI thread — as the number of trips across the bridge can largely impact performance, which can be particularly noticeable when it comes to animations. Therefore, in React Native, we use dedicated animation tools that are optimized for native performance while providing a JS-based API for declaring animations and interactions, such as Reanimated or React Native's
Creating smooth, optimized gestures and animations is a challenge on both the web and in React Native — but the challenge is a bit different due to the fact that the gesture and animation primitives in each ecosystem vary so significantly. If you're moving from a React Web background into the React Native space, plan to spend some time understanding how React Native's touch gesture and animation systems work and how tools like React Native Gesture Handler and Reanimated can help you build out custom gestures and animations.
Different Deployment Strategies
Web apps are served over the internet via web browsers which means, for now, there's not much more than engineering efforts standing in the way of you and your users. If you'd like to make a web app full of dad jokes and silly memes, you could create that app, push your code assets to a web server, and start showing people your awesome web app. This is a comfortingly "free" process, somewhat exempt from strict rules set forth by non-elected governing bodies (for now). When moving to mobile application development, the deployment process is not nearly as "free."
Since mobile applications are distributed through the Apple App Store (for iOS applications) and the Google Play Store (for Android applications), you have two options: play by the rules set forth by Apple and Google, or don't ship a mobile application. Your mobile application must be approved by Apple and Google to ship to your users, and every subsequent update must also be approved. This manual approval process generally entails much slower deployments that depend on a third-party's (sometimes confusing) approval or rejection — something that can be quite frustrating if you're used to your code passing all tests and immediately being shipped to production and available to users.
Overall, getting mobile applications prepped for store approval is a challenge quite distinct from the challenge of standing up, stabilizing, and securing a web application. If you're new to the mobile application development space, you might initially find this aspect of mobile development a bit of a turn-off. However, this is the unfortunate reality of getting native mobile apps into the hands of your users. But, to be fair, Apple and Google have been making attempts to protect user privacy, which requires stricter approval processes — this is something to keep in mind the next time you get frustrated with a slow store approval (or perhaps a confusing rejection).
Different Testing Approaches
When it comes to front-end testing on the web, there are a lot of great testing tools available: Jest for running tests, react-testing-library for testing React components, Cypress for E2E tests, and so on. Testing web front-ends is a challenge in and of itself, even with solid tools available.
Testing React Native poses many of the same challenges, and then some. Since test runners and testing libraries generally execute in a Node environment, your dependencies need to be JS. In React Native, many libraries have both JS and native dependencies — and these native dependencies tend to choke up these JS-based test runners (since Node doesn't know how to execute, say, a Kotlin file), requiring you to manually mock out any native dependencies you might stumble upon while writing tests. If you're used to writing automated web front-end tests, and attempt to create similar automated tests for a React Native application, you might find yourself encountering significantly more friction and writing many more mocks.
Tools like Cypress and Test Cafe have made writing web E2E tests much more approachable (and arguably even fun!) than it used to be. These test runners execute your web app inside of a web browser and allow you to programmatically interact with your web app and then assert conditions about your front end. There are tools in the React Native space, such as Detox, that have a similar goal of making E2E tests approachable — however, this space isn't nearly as mature and the steps required to get something like Detox working, working consistently, and running in a CI/CD pipeline are far from trivial. From my experience, E2E tests in the React Native space are found much less frequently than in the React Web space due to the added cost to create, run, and maintain them.
Due to the challenges I just mentioned, I've found it common for React Native developers to test core business logic and utilities (that have no native dependencies) using familiar tools like Jest, and manually test the UI (which is often the job of a dedicated QA specialist). Overall, you might find the testing process for React Native applications much more manual than what you're used to with React Web.
Although there are plenty of differences between React Native and React Web, there are also key similarities that will make an engineer with experience in React Web quickly feel at home when developing with React Native.
First and foremost, both React Web and React Native are underpinned by React. So although the underlying platforms/targets are different, the UI framework is the same. This entails that most mental models regarding React apply equally well to React Native as they do to React Web (e.g., component creation, JSX, hooks, lifecycles, etc.). Since writing React Native and React Web apps both involve writing a lot of React components, this shared mental model of React goes a long way in bridging the gap between ways of thinking needed to develop React Web apps and React Native apps. An engineer coming from React Web doesn't have to additionally learn about the specific paradigms of iOS and Android apps, since the React abstraction bridges that gap.
JS and Accompanying Ecosystem
Since React Web and React Native are both underpinned by React, and React is a JS library, both contexts involve writing primarily in JS (or TypeScript). This means that a React Web developer doesn't need to learn new programming languages (like Swift or Kotlin) to start building mobile apps.
With JS being at the core of both React Web and React Native applications, there's a subset of the JS ecosystem that can be used in both contexts. I already mentioned that there are plenty of libraries that are web-specific and will not work with React Native, but there's also a fair amount of libraries that are relatively agnostic to their execution environment and therefore work in both web and native environments. This includes state management libraries like React Query and Redux, GraphQL clients like URQL, and even date utility libraries like date-fns.
With a fair amount of overlap in ecosystems between React Web and React Native, there is some potential for code sharing between contexts. At the very least, familiarity with some tools in the React Web ecosystem will serve one well when developing native applications with React Native — since both web and mobile applications involve things like state management, network interaction, and date operations.
Bundling and Tooling
React Native packages a JS app into a native container in a sophisticated way. One benefit of this approach is that the core application logic lives inside of a JS bundle, and is handled by Metro — a JS bundler for React Native. This JS-bundle approach means Metro can offer a lot of the niceties you might be used to on the web, like hot reloads — a feature you wouldn't generally expect in a mobile application development environment.
Beyond bundling, a lot of the general developer tooling is the same in the React Web and React Native environments. Since React Web and React Native are both JS-based, linting tools (like ESLint), formatting tools (like Prettier), static-typing tools (like TypeScript) and test-runner tools (like Jest) behave the same way. This entails that the developer experience in React Native is similar to React Web once all of your native setup is handled (like installing native dependencies, ensuring you've got a device to test on, etc.).
Although CSS and React Native's styling API are quite different, React Native has adopted Yoga Layout, a library that provides a cross-platform FlexBox-like layout implementation. Those experienced with FlexBox in CSS will find that React Native's flexible layout system behaves similar enough to FlexBox to feel comfortable with the basics of creating UI layouts in React Native. It's worth noting that in React Native, all elements have a default "display mode" of "flex" (no need to specify it), and the default flex direction in React Native is
column (as opposed to
row like in CSS).
React Web and React Native are not the same. They are both underpinned by React, but they execute in fundamentally different environments and therefore have a different set of constraints. We saw that the differences between the browser environment and native mobile environment entail different methods and paradigms for styling, navigation, gestures and animations, deployment and testing — and different sets of ecosystem libraries available. Some of these differences are drastic, while others are subtle yet noticeable.
With all of these differences in mind, React Web and React Native share a very important aspect: they are both powered by React and JS. This entails that the React mental model for developing UIs can be leveraged in both environments (while keeping in mind the differences we've already discussed). This is quite powerful, as this mental model can be used to develop UIs on both iOS and Android without learning new UI paradigms for both native platforms. With React Native, React Web engineers can leverage their JS expertise and forego learning new languages that are generally needed to create native mobile applications (such as Swift and Kotlin). Furthermore, there's a growing set of JS libraries that are agnostic to their execution environment, and therefore can be used in both React Web and React Native applications, which means some of your favorite tools from the React Web ecosystem can be used in React Native applications (although don't expect all of them to!).
Overall, if you're a React Web engineer looking to build native mobile applications, React Native is a great choice. You should be able to leverage a lot of your existing expertise in the realm of JS and React. But be fully aware — web applications and mobile applications are fundamentally different things, even if both are being powered by React!