Switching from Front-End Web Development to React Native — Six Months in Highlights

Radoslav Popov
Jun 15 · 12 min read

Six months ago I took on a role of a JavaScript developer in a team building a React Native app. I’ve been building web sites and applications for 12 years before that, using both vanilla JavaScript and frameworks extensively, including React for the last 2 years.

In this article I summarize how developing with React Native compares to developing with web technologies in the browser based on my impressions for this half a year, and what you can expect if you decide to get deeper into the native world.

View Layer — from DOM to Native Components

The first striking difference between React Native and web development is the lack of HTML (or DOM) elements. Instead of building layouts with <div>s and <span>s, or whatever semantic tags we need in general, we are limited to using specific React Native components that we import from the react-native package, like <Text>, <View>, and <TouchableOpacity>.

As you may have already guessed, <View> is like <div>, and <Text> is like <span>. But this is not the full story. A text can only be placed inside a <Text> component, and an event handler like click (called press in React Native) can only be attached to specific components called Touchables (e.g. <TouchableOpacity>).

To put it in perspective, imagine we want to have a <div> with event handlers, styling, and nested text. In web, the <div> is more than enough for accomplishing such a simple task. Using more elements would even be considered unnecessary complication, as it makes the code more difficult to read.

In React Native, we are forced to use more elements.

Sounds weird already? React Native’s built-in components are way more restrictive than the DOM. This makes sense when you think that historically, browsers have always been error-tolerant due to the vast amount of poorly written web sites and lack of standards in the 90’s and early 2000s. The same is not true for Android and iOS, which both have their own strict way of structuring layouts. React Native is just trying to provide common components that can be translated natively to them, but when coming from the web development world, you may find yourself using too many components and nesting for something you are used to be able to do straightforward.

Grade: B-

TLDR: React Native comes with its own view layer components that replaces DOM elements like div and span, but they are way too restrictive, which may result in additional nesting and complexity.

Environment & Debugging that is Way More Inferior

Native developments pushes us away of our beloved browsers to emulators, simulators, and physical devices, which changes completely the way we create and test software.

Fun fact: there is a difference between a simulator and an emulator.

First, to be able to build any kind of an iOS app and to load it on a simulator or an iOS device, we need a Mac. There is no workaround. Full stop.

Then, we need both xCode & Android Studio, and some additional installations that comes with them, although we actually use the React Native CLI to build apps. This is quite a cultural shift from the web environment where we can run a web app in the browser without having any prerequisites regarding software installations.

iOS developers use xCode to build iOS apps.
Android developers use Android Studio to build Android apps.
React Native CLI tries to abstract these two IDEs, so we don’t need to bother with them, but this is not always possible. We sometimes need to install dependencies through them, load emulators, or configure stuff.

Once we actually load our app, we need to connect the simulator/emulator/device to a dev tool. Google Chrome is used by default, and it looks very similar to what we are used to have in web, but I quickly moved away from it because once connected, Chrome actually changes the way HTTP request are performed in React Native. Yes, this means that my app works differently depending on whether it is connected to Chrome for debugging or not. This was a no-go for my specific app, and it is certainly not appropriate for a lot of cases, so I actually started using an external software called react-native-debugger. It looks very similar to Chrome dev tools and it comes with built-in Redux support, so this is fine.

However, debugging experience in general is way more inferior to what we get with a real browser.

First, it is slow.

Second, inspecting the React component tree is not straightforward, as it shows many native components in addition to the ones we have in our source code.

Third, the app often crashes while connected to a debugging. Sometimes a simple reload fixes the problem. Other times all we can do is uninstall & install the app again on the device.

Fourth, changing styling properties in the inspector doesn’t always work as changing CSS properties for a web page, so working on the visual design is somehow slower and more tedious.

Fifth, the Live Reload functionality is buggy. Sometimes we need to reload the app to see our code changes.

Sixth, one cannot just resize an emulator, simulator, or a device to see how the app looks on different resolutions, the same way it is done with browsers. We need to load the app on an emulator, simulator, or a device with a different resolution.

Still, there is a JavaScript console tab which works the same as the console tab of Chrome, so printing messages with console.log is a viable option, as well as executing arbitrary JavaScript directly in this tab. The network tab is also identical to the one found in Chrome, so inspecting network requests is straightforward.

Grade: C-

TLDR: Developing native apps with React Native requires an environment and tools that, although looking familiar to web developers, are way more inferior to what we have when developing web apps in the browser, which increases dev & test time.

React, Redux & JavaScript Do Work out of the Box

Tools like babel, webpack, create-react-app, and concepts like transpiling and source maps work so well for the past couple of years, that we sometimes forget different browsers support different JavaScript & JavaScript APIs.

React Native also uses babel to perform a small transformation step before sending JavaScript through the React Native bridge, so we can use language features not yet implemented. In other words, with React Native we always use the latest, most modern JavaScript. Cool!

Once we stop being distracted by the lack of DOM described earlier, we realize React is the same framework we use in web, with concepts like state, props, hooks, and patterns like HOC working exactly the same as we know. The same goes for Redux, which makes it possible to organize our architecture in a familiar way.

There is just one small twist to this. The React version used together with React Native is a couple of versions behind the official React release, which means we start using some new features with a bit of a delay, usually a couple of months. This happened to me when hooks were officially introduced in the stable release, but I needed to wait a bit before using them.

Grade: A

TLDR: React, Redux & JavaScript don’t change a bit when we do native development, which makes it possible to reuse familiar technologies and patterns.

Primitive Styling Gets us Back a Decade Ago.

The one single thing I miss the most from the web environment is the lack of CSS in React Native.

When it comes to native development (Android & iOS), there is nothing remotely similar to CSS for building layouts and reusing visual logic.

React Native tries to make it more comfortable to web developers by implementing parts of CSS. Its biggest achievement is the implementation of flex which works really well and is used extensively due to the lack of any other functionality for building layouts. Forget about display inline, inline block, floats, and grids. Relative positioning is by default. There is no static and fixed positioning. Absolute positioning is supported, but is sometimes buggy, and because all components are relative by default, an absolute element can only be positioned in relation to its parent.

The limited functionality is already a bit disturbing, but what bothers me even more is the way this “limited CSS” is declared. Basically we need to provide inline styles to every component, and there is no way to reuse styles with something like selectors or cascading. Styling every component by its own is what makes me feel I get back at least a decade ago.

To put it in perspective, imagine we want to define common styles for all texts throughout the application — something very basic and common. In web, we can declare them as part of, lets say, html, body { ... } selector, or a span { ... } selector, or even a .text { ... } selector. In React Native we need to pass inline styles to every <Text style={{ ... }}> component. In order to circumvent this limitation, it is usually recommended to create an <AppText> component which encapsulates the common styles, and is then used across the application.

Also, styles in React Native are created in JavaScript objects, not CSS notation, which is a bit annoying. The way to reuse styles in React Native in general is to keep these objects reusable, but they still need to be provided inline to components.

According to some people, however, not utilizing some powerful CSS features we’ve been using for a long time to reuse styles across components is not a bad idea at all. There is a modern trend called CSS-in-JS which stands for using exclusive styles per component. The idea is that with component-based development, we move away from basic elements that don’t have state and own styles, so it no longer makes sense to have common application styles. A library called Styled Components, first created for React, embraces this concepts and enhances it with additional features, so we, web developers, feel more natural in React Native world.

Whether CSS-in-JS is indeed a best practice is a debatable topic, but given a chance to start a React Native project from scratch, I would definitely use Styled Components.

Grade: D+

TLDR: React Native supports limited CSS functionality and only inline styles, so we lose concepts for reusing styles like selectors and cascading. CSS-in-JS is an emerging best practice that can help.

jQuery-style Animations Bloats our Code

In relation to the lack of CSS in React Native, it is worth mentioning that there is nothing remotely similar to CSS3 transition and animations, except, with a lot of limitations — the very scarcely documented (and experimental on Android) LayoutAnimation.

This means that usually we are stuck to the Animation API, which looks very similar to the way we used to programmatically define animations with jQuery before transitions and animations emerged in browsers.

Sometimes we can use a native component that comes with its own animations. This is a big win when creating a native app (compared to a hybrid one). But in all other cases, when you need a custom animation, switching from declarative syntax in CSS to imperative syntax in components bloats our code, increases maintenance time, and can lead to more bugs.

Grade: D-

TLDR: Working with animations in React Native is like working with jQuery in the past— there is nothing remotely similar to the declarative syntax of CSS3 transitions and animations.

Native is Unfortunately Unavoidable

The big promise of React Native is that we will be able to reuse our skills as front-end developers to build native apps without having to deal with Java/Kotlin and Objective C/Swift. Unfortunately, native is all around us when using React Native and is sometimes unavoidable.

In order to have any kind of “native” functionality (e.g. working with files), we need to install the so called “native module”. The process is similar to installing a pure JavaScript module (e.g. redux) through npm, however, there is an additional step of linking this module to the native part. Sometimes the linking process happens automatically by executing react-native link, but many times this command doesn’t succeed, which forces us to dig into the native part and add some stuff there.

These additions are usually not a big deal, but it can be annoying for a JavaScript developer to have to change Java code or a gradle configuration.

So how often are these changes necessary? Well, the documentation of every native package clearly describes how to link it manually if “the automatic linking doesn’t work for you”. So you can imagine it is very often.

Despite of being new, the React Native community is actually very strong, and we can find a lot of packages out there. However, if we need something that doesn’t exist, or no package works for our case, there is no other way around but to implement it with Java/Kotlin for Android and Objective C/Swift for iOS. This didn’t happen to me during these 6 months, but it will certainly happen once we need a more custom solution. A JavaScript developer implementing a native functionality is also not a very clever business strategy.

Grade: C

TLDR: To edit native code or configuration unfortunately happens more often than a JavaScript developer may want.

Pixel is not Pixel

Well, this one is not related to the framework so much as to the way phones and tablets are built, but it reflects our development experience when switching from web to React Native a lot, so I need to mention it here. When we don’t use flex to define sizes, we use independent units instead of pixels, ems, rems, or pts. What is an independent unit exactly? It is dependent on the pixel density. Due to that colossal difference, we no longer speak of fixed-size components, as everything becomes relative, but not to the screen as we are used to.

As a result, the same component can look significantly larger in one tablet than on a similar-sized tablet. You may wonder why we can’t just define everything in flex. First, flex works on one direction. Second, text sizes cannot be defined in flex. Third, some components are dependent on their content, which in turn can be dependent on the text size.

Due to this relativeness, creating a responsive design is very hard.

Some people try to find their own way to circumvent these limitations and create a responsive design (I wouldn’t call it responsive, though — maybe self-expandable?). But it is sad such a fundamental solution isn’t built in the framework itself.

Still, the fact that building layout for native is different than web doesn’t mean it is bad. It is an entirely new world and I actually expect to grow to like it in the next half a year.

Grade: B

TLDR: Units used in React Native are dependent on the pixel density, which makes every component relative, but not to the size of the device as we are used to in web. Responsive design is hard to implement and is often avoided.

One is Never Bored with React Native

It is important to stress that React Native is not to blame for all the shortcomings one can find comparing it to web, and it is a bit unfair to have this comparison in the first place. Browsers have evolved so much during the last 20 years, that no platform is able to match their capabilities. Also, Apple and Android are two giants that don’t necessarily have an incentive to make cross-platform development any easier.

Many things I mention in this article sounds like I am frustrated with React Native, but believe me, that’s not the case. Actually, although I miss the web, I am very happy I switched to React Native in the first place and had the opportunity to explore an environment that is so different, with all its quirks and specifics, which opens a completely new door for me as a software engineer.

When dealing with React Native, we are never bored. Whether we spent hours trying to find why an absolute positioned element is not clickable, or decide to quickly update a minor version, and after 2 days find ourselves still fixing other dependencies in order to run the app, the challenges are everywhere. If that is not exciting for you, then think twice before joining the React Native community.

Radoslav Popov

Written by

JavaScript Developer, specializing in creating custom JavaScript apps.