React Web vs. React Native

When I pivoted from web development to native development, I was like no problem! It’s still React, Redux, and all that fun functional programming right?

Turns out this perspective was decidedly optimistic.

Even though React Web and React Native look pretty similar, under the hood they are really quite different. It’s like driving automatic vs. stick. The two cars might both look shiny and similar, but once you start driving them, they run on engines that are built very differently. Here are some differences and lessons I wish I knew before I started.

React Native is notorious for its build issues, but I didn’t quite understand why until I dove into it more:

  • JS build with package.json
  • Cocoapods with Podfile (for managing Swift and Obj C dependencies)
  • Connecting the two and other libraries with react-native link or manual install

With React Web, you really only have to worry about your JS build, probably webpack, babel, and some other goodies. However with React Native you have to make sure that the two separate systems are building correctly together, which causes major headaches. Xcode also has some pretty bizarre caching issues, which requires constant cleaning.

RN tooling is at its infancy compared to web, or even native development. This means a smaller community that is actively debugging and resolving issues.

On web, generally you can get away with just using or a HOC you’ve built on top of it. Then you can style and render whatever items you want. However, on React Native, you have a lot more options:

  • FlatList which are the preferred way to display arrays, powerful and virtualized and displays items lazily
  • SectionList which is used to display multiple lists, often heterogeneous, on one page
  • VirtualizedList which is what FlatList and SectionList are built on
  • ScrollView which allows a scrollable view (not just for lists, but useful). However, be careful — ScrollView always render all child components at once.
  • ListView which is now deprecated.

Each of these has its own very long list of optional props, which require some getting used to and diving into documentation.

The additional structure required for React Native has its tradeoffs. Many of its built-in props are a great way of quickly structuring your app with minimal work. However, you sacrifice the flexibility of web where you have write a lot of that boilerplate yourself.

This is more of a personal weakness, as I have a much stronger mental model of how the web works compared to native development. Since I have experience with HTML, native JS with DOM manipulation, etc. I have a much better mental model of how React translates to code that runs on Chrome’s V8 engine.

However, I didn’t have the same background with Objective C or Swift. While that doesn’t matter 90% of the time when using React Native, that extra 10% can really make a big difference when approaching the boundaries of what React Native can do, edge cases, thinking about assets, as well as diagnosing performance issues.

A good example of this is how assets are fetched and loaded. In web, all assets are fetched over the network, which means that optimizing for minimal assets, compressing data over the network, etc. is really important. However, in native development, you fetch dynamic assets over network, but static assets are bundled during app download.

This fundamentally comes from the way that React Router vs. React Native Navigator works.

On web, it would be ridiculous to think that your entire website is always rendered at once. However, this is exactly the case with native! With React Native Navigation, all main screens are always mounted and rendered. This allows the user to have a seamless optimized experience when you’re switching between tabs if you use TabNavigator. In addition, you’re going to be layering navigation if you have modals or screens that stack with StackNavigator.

If your native app contains a lot of meaty data and heavy use of lists, this can be a pain to make performant. Whenever the user performs an action on one screen, this can lead to a lot of side effects on other screens. This requires to be really diligent with understand when and why re-renders happen. This includes avoiding arrow syntax in renders, which I used as often as possible on web.

Since we use Redux Saga, Reselect, and Recompose libraries, it requires a better understanding of how props are evaluated and diffed. It also requires heavy use of React.PureComponent and shouldComponentUpdate if you can’t figure it out in the Redux layer. We are using an Immutability library and even that has its tricky gotchas.

In web, even if you aren’t following performance best practices, it wasn’t a huge issue since typically only one or a few screens were mounted at once — but on native, that number could be dozens! Sometimes pages were re-rendering deep on a navigation stack that the user wasn’t even viewing.

This also means that native requires much more rigorous optimistic UI, as users have much higher standards of immediate feedback on native versus a website, especially when they are on their phone.

To be fair, you no longer have to worry about Internet Explorer, thank goodness.

However, it quickly became one of the quirkiest parts about RN is that you essentially have different code engines running with multiple configurations even if you are just talking about iOS:

  • Debug Remotely turned on means that you are running Google’s V8 JS engine
  • Normally, RN uses JavscriptCore which is Safari’s engine. Debug and Release builds on Simulator have slightly different code optimizations, leading to very frustrating bugs. As it is very difficult to debug on Release since well, it’s a release build.
  • Even the engine on a physical device differs from simulator. Frustratingly there are many bugs that happen only on iPhone 6 and earlier.

It is much easier to not have to worry about responsiveness across all screen sizes, from large desktops to tiny phones. However, native still has its fair share of permutations that can go wrong — and you still have to account for multiple device sizes, as well as tablets.

I haven’t done Android yet for React Native, but I’m sure that’ll cause its own share of headaches, as support for Android is not nearly as comprehensive.

While the majority of styling with React Native mirror their React counterparts, they also often don’t.

Both make heavy use of Flexbox, but positioning things are quite different, and also have different edge cases. For example, React Native only has position: relative (the default) and position: absolute

The tooling for Inspector is also much more powerful on web than native. It’s very easy to style things and change content in a very playground way on web. On native, it’s much harder, as you have to dig through a giant nest of components. Any typo or invalid style will cause the simulator to crash.

With animation, most of the time I used CSS if I could get away with it. However, on native you have to use React Animation or another library, which has its own learning curve.

This isn’t something that necessarily a difference between React and React Native, but rather a user behavior difference of web vs. native.

On native, users are constantly horizontally scrolling and generally making use of a lot more gestures that you have to worry less about on web.

Since scrolling is handled through refs, this means no state! When you want more complicated scrolling behavior across two or more components, good luck. Two way data binding leads to some odd side effects. The same applies to some weirdness with focusing.

It would be amazing to see better scrolling support for React Native, given that users are having higher expectations for seamless, animated scrolling experiences.

There’s a lot more differences, such as release cycles, code pushes, analytics and logging for another time. Stayed tuned for more!

Design & Dev in SF | Maker at heart