Which Multi-platform framework should I use to write my app? Let’s try React Native.

David W. Gray
8 min readSep 11, 2023

--

The one where I do my best to quickly cobble together my sample app using React Native and share my first impressions.

This article is part of a series comparing some of the top multi-platform frameworks. While I hope this works reasonably well as a stand-alone intro to React Native and Expo, you’ll get more out of this if you go back and read the introductory article describing the overall effort and the app I’m writing.

Top-level capabilities

The core React Native project supports Android, iOS, and TV variants for both operating systems. Third-party support exists for the web, Windows, Mac, and Linux. React Native, despite the name, is slightly less native than MAUI or Flutter in that your code is written in JavaScript, which may JIT to native code but doesn’t get built ahead of time. However, it is still native in the sense that it’s not just an embedded browser control like Ionic and similar solutions.

First impressions

The very first thing I noticed about React Native is that they strongly recommend using Expo to facilitate creating, building, deploying, and publishing React Native apps. So, I’ll be evaluating Expo + React Native combination. With the help of Expo it was incredibly easy to get an app up and running and testable on my physical devices and emulators.

Installation was smooth on both Windows and Mac. The documentation is well written, but despite including a quick introduction to React, it is very oriented to developers with existing React experience.

One would think that the most challenging thing about using React Native would be that I have to learn React first. But, for me, React is close enough to vue.js, which I am familiar with, that a quick run through React’s quick start guide, including the tutorial, gave me enough to get started. The thing that actually had me stumped for a bit was the lack of core components. It didn’t seem like they had the necessary controls to build even a simple app like mine. But there is a rich ecosystem of libraries; the React Native site even gives some guidance as to how to find them. It was just buried enough that I didn’t find the guidance until I had spun for a while and located a couple of libraries on my own. Once I pulled in a component library, things clicked into place quickly.

React Native’s Fast Refresh is slightly different from Hot Reload in the other frameworks, but it fills the same place and works quite well. As I’ve noted, for all frameworks, some form of quick refresh when editing design and code is a must-have for modern applications.¹

My initial study included going through React Native’s getting started , Expo’s get started documentation, and React’s quickstart. Once I did that and identified that I needed an external source of some components, I was ready to dive in.

Development environment

VS Code is the development environment of choice combined with the React Native Tools extension. This was easy to set up and get things running. As I mentioned earlier, I used Expo to create the app and get started.

Components for everything?

As I noted above, building a React Native app is very much about identifying and assembling the components needed to render the app.

Given how slim the core component library is and how rich the third-party component ecosystem is, writing a production app in React Native will most likely involve researching components and ensuring that the libraries one consumes are stable and well-supported. This is true of most modern applications, but the inflection point for React Native was earlier for me than for the other frameworks I’m evaluating in this series.

Install and deploy to targets.

Installing and deploying worked smoothly using Expo. That didn’t result in actually deploying the application to a device natively. But other than being able to show off the app to friends and family before publishing the app to the app stores, I’m not sure much is lost. One little gotcha is that as of this writing, you have to scan the QR code from within the Expo Go app on Android, while on iOS, you have to scan from the camera app. That had me running in circles for a bit when I first tried to deploy to my iPad after having spent time working with Android.

Debugging tools

The debugging tools built into Expo are pretty cool. They include several variations on element inspection that give dimensions and attributes. Since I also enabled deploying to the web, I could use the JavaScript debugger in Chrome/Edge as well as all the other developer tools I’m used to for web development. But it also looks like you can open a JavaScript debugger against an app running on the emulator or a device.

The App

I’m not going to go through a step-by-step description of how I created this app; you can look at the code on GitHub, which I highly recommend as a way of orienting yourself for this discussion.

Below are screenshots of the Android and iPhone versions of the application. Note that they are identical except for the slight differences in the frame.

Android Version
IPhone Version

I started by creating an app using Expo. The documentation talks about having first-class support for Typescript, but it took a little to figure out how to get to the Typescript template. To save you the trouble, the incantation is as follows:

yarn create expo-app -template expo-template-blank-typescript react-beat-counter

I then installed React Native Web and React Native Paper as the library I landed on with very little research that provided the Segmented Button Control that I needed and a slightly better version of Button. Given more time, I’d play with other libraries as I’m not thrilled with Paper’s implementation of Button.

Once I installed the libraries, I did my initial round of development by laying out the controls on my main View. This involved placing a Paper Button for the counter, Views for the MPM and BPM, and Segmented Buttons for the meter and count method controls. I then played with flex layouts and poked at styles until things looked reasonably good. I finished up by adding a Paper AppBar.Header to get the app’s title up at the top like the other versions.

From there, I iterated to get the functionality working. This went remarkably smoothly. In part because, while the details are different, the general philosophy is fairly close to the vue.js that I’m used to. The only gotcha that I ran into was with the timer callback. Since the timer callback function created a closure for my state variable if I used it directly, I was always reacting to the initial state rather than the current state. The solution to this was to access the state variable via a reference. I did this using the useRef hook, but I could also have made a state reference class to hold the state enum.

The other thing that I’m not thrilled about is how I implemented the state management for my settings. I had to manually parse/format my enums when interacting with the segmented button control since it only accepts/returns strings.

<SegmentedButtons
value={method.toString()}
onValueChange={(s: string) => updateMethod(parseInt(s))}
style={styles.option}
buttons={[
{ value: CountMethod.Beat.toString(), label: "Beat" },
{ value: CountMethod.Measure.toString(), label: "Measure" },
]}
/>

This may be an artifact of Paper — perhaps another library will have more sophisticated state management. But the other issue was that with the useState hook, I could not override the setter of my state property, so I had to create an update method and use that by convention. This works fine in my small application, but this method would leave a gaping hole for someone to use the setProperty function directly and cause confusion and bugs in a more extensive app.

  // Update the count method and recalculate the intervals if necessary to keep BPMs consistent
function updateMethod(value: CountMethod): void {
switch (value) {
case CountMethod.Beat:
convertIntervals(meter, Meter.Beat);
break;
case CountMethod.Measure:
convertIntervals(Meter.Beat, meter);
break;
}
setMethod(value);
}

Finally, I wasn’t happy with the configurability of the React Native built-in buttons or the slightly more versatile Button included with React Native Paper. I wanted to change the font size and both center and wrap the text. This apparently goes against Google’s Material Design guidelines, but it worked easily in Flutter. I would have let this detail slide since I’m just doing a quick evaluation. But when I went to take screenshots of the iOS version, it looked awful. So, I tried for the minimal fix to get out of the butt-ugly state. My first round was to identify a stand-alone button component that looked promising. But it didn’t include typescript definitions, and there weren’t definitions on definitely typed. So the easy fix turned out to be to style a TouchableOpacity Component manually, which wasn’t too hard and got me to a good place. That does reinforce that I’d spend some real time evaluating which component library to use as my primary source if I move forward with a React Native implementation.

Typescript

I chose to use Typescript rather than directly using JavaScript. I believe using Typescript is the correct answer in place of JavaScript wherever possible. There is no loss of performance since TypeScript transpiles to JavaScript, and having some form of compile-time type checking has saved my butt too many times to count. It looks like there are some holes in pre-built types for supporting libraries, but I would take the pain to generate the .dts files myself for anything I really needed.

Conclusions

React Native is tightly tied to React, so if you or your team is most familiar with that framework or similar frameworks like vue.js, this is a great choice. With Expo, I found install and deploy to be the easiest of the three solutions I evaluated. And the Expo Go app is fantastic, especially when it comes to enabling a smooth diagnostics experience on devices.

Since the built-in react native components are so sparse, it has a similar effect as Flutter in that the components that one actually renders are from higher-level libraries that will look and act the same on all systems. This has the same pros and cons as Flutter’s native rendering of everything with widgets.

I would also want to check out the third-party support for Windows and Mac applications, as both competing frameworks provide that built-in.

¹ We called this RAD (Rapid Application Development) back in the day at Microsoft. RAD was a feature that Visual Basic was centered around and was a big hit with developers. Trying to get close to the RADness of VB drove various forms of incremental linking and “Edit and Continue” in the VC++ and .Net teams. It looks like they’re finally achieving the rapid turnaround VB attained with an interpreted environment in a compiled environment with Hot Reload. However, the ability to integrate a design surface with code still appears to be elusive.

--

--

David W. Gray

I am a software engineer, mentor, and dancer. I'm also passionate about speculative fiction, music, climate justice, and disability rights.