Good Practices: Why you should use JavaScript whenever possible with React Native

Brent Vatne
Exposition
Published in
6 min readJan 3, 2017

--

When does it make sense to drop down to native, and when should you stay in JavaScript? I believe the decision isn’t as simple as a one-off technical decision of what is easier to do in the moment — the future of React Native as a community depends on the decisions that we all make when building our projects and publishing components, and we need to make decisions with a longer term vision in mind for where we want the community to go.

Instagram stories is a popular UI for people to re-create with React Native: example 1, example 2.

Examples 1 and 2 respectively.

@tlackemann wrote example 1 in Objective C and leveraged iOS APIs that aren’t all exposed in React Native core (it doesn’t make sense for them to be). @yousefkama wrote example 2 and he had to resort to implementing a different UI because it didn’t seem possible from JS from his research. I’ll be the first to admit I know little about 3D graphics, but it certainly seems like the effect should be possible from JS in React Native.

But why use JS if you can do it natively?

This is the same question that anyone could ask about React Native. Yet many people who are convinced about the benefits of using React Native for a project are not always persuaded that they should attempt to write as much of their app as possible in JS.

If it were implemented in JS, it would be cross-platform by default. It would be easier for the React Native community, who usually aren’t particularly experienced with ObjC or UIKit, to remix the code and create their own transitions. It would allow you to ship updates for this feature over the air, to include less native code that could potentially break when the React Native’s native APIs change (much more common than breaking API changes in JS), and many other benefits that we’re all very familiar with. I think it is clear that a pure JS implementation, when possible, is universally preferable.

Perhaps react-native link should present you with a warning before
running:

Warning! react-native-example-library is adding native code to your
iOS and Android projects.
- If you add this code, you should trust that the maintainer keeps it up to date with new releases at the same speed that you expect to, otherwise you may not be able to update your project until they do.
- If you update the library to add some new features, you must re-submit your app to the App Store and Play Store. If you ship an
OTA update, your app may crash for your users.
- If you do not know any Objective-C or Java, you may run into problems that you don’t know how to solve. It may be difficult or impossible for you to customize.
- You may be able to do this with pure JavaScript instead! If not,
consider offering to help the author upstream this code.

So I tried to rebuild it in JS and got close, but…

I used an Animated.ScrollView with Animated.event and useNativeDriver to perform the animations, so there is no lag between scrolling and animating. I then apply transforms to each of the views based on the scroll position. Unfortunately, I believe (again, I am a 3D graphics idiot, correct me if I’m wrong!) that React Native needs support for translateZ in order for this to work. (See source)

Update: Gonzalo Aguirre managed to get this working without translateZ! See the source here. Currently only iOS is supported but once this PR lands to add perspective support to Android it will work without any changes. You can also try it with Exponent.

Getting to the point

I think this is a common problem that React Native developers face — you can almost build something in JS, but React Native core is missing a feature that you would need to do so, and there is no general purpose library to address the problem. Quite often the response by the community is to go off and re-create the entire thing natively and call it a day. I don’t think this is entirely a bad thing; it exposes current pain points in React Native development when projects like this pop up. But we need to be careful and honest with ourselves as a community that this is not an ideal implementation of a library; if we were able to achieve the exact same result in JS, it would be preferred. The native approach is a hack to work around pain points in React Native. Native plugins should be used to expose low level general features that you can then build on top of from JS.

Other examples

Another example of this is a material design library that implements the material spinner natively for iOS because there isn’t currently a way to loop animations entirely on the UI thread. The best solution for the community would be to implement Animated.loop and the native driver for iOS and Android. This would slow down development of your personal project slightly, but you’d be giving back in a big way to the community and improving React Native for everybody, and the next time you need a custom spinner you can write it in JS.

react-native-fetch-blob is a library that is a somewhat faithful polyfill of the browser’s Blob API. So if a React Native developer wants to upload images to Firebase, they need to first learn that they need to install this library, install it, and do any specific configuration necessary to make it work with their project, and then they can upload files. This is decidedly worse than the browser workflow, where the API is already there and all you need is the Firebase JS SDK. (Note: react-native-fetch-blob is a well-made general purpose library, I’d just love it to be part of core in this case!).

So when people come up with one-off native solutions, I completely empathize with the need to ship something, but at the same time feel sad that the development time isn’t directed instead towards improving the core or a generalized solution.

How you can make the React Native community better

If your company benefits from using React Native, you could hire a
contractor to generalize the implementation for you and ship it upstream. At Exponent we’ve done this many times, it’s why you have useNativeDriver for Animated (Krzysztof), requestIdleCallback (Janic), and tens of other features that you may use in your app today.

Native animations were driven by a need to have performant navigation on
li.st for Android without falling back to implementing navigation natively. requestIdleCallback was needed to implement a contacts list screen for Brighten, so you could scroll through between 100 and 100,000 contacts and not end up with an empty screen as the JS tries to keep up with the scroll position, rather than bridging an existing native contacts view (which would inevitably end up with 20–30 props and still not be as customizable, portable, etc).

We also recently worked with Satyajit, Mike to polish up some existing work on a Blob API for React Native and get it upstream, which will make the experience of uploading files through Firebase (and a bunch of other things) more pleasant out of the box.

Next time you consider reaching for native code to solve a problem in React Native, think about what exactly is preventing you from doing it in JS. Please remember that upstream improvements will pay long-term dividends for yourself and our community. Hiring a contractor for a few hours can be an excellent way to upstream a generalized version of native code you’ve used to ship your app. App & Flow, Callstack.io, Apperson Labs, Infinite Red, and Software Mansion are all good choices.

It’s up to the community to shape the future of React Native. I hope we can all agree that the best possible future is one in which the true benefits of React Native have been realized. One where you don’t need to depend on countless native plugins with varying degrees of stability and cross-platform support. A future where you rarely, if ever, need to touch or maintain native code.

I need to make some time now to learn about and implement translateZ.

--

--