Devex

Having worked with React Native projects on and off for years now, I’ve come to appreciate that there are significant productivity and developer experience (devex) gains on the table, that tend to be derailed the moment a native library is added to the mix. But what if you could keep that productivity flowing?

Most people (somewhat rightly) think of Expo Go as the training wheels that nobody uses for serious React Native development. But you’re probably like me: the vast majority of daily work is simple Create-Read-Update-Delete (CRUD!) data manipulation. And what if, for that daily work, we didn’t need to fight with getting Xcode or Android Studio to compile, code sign, deal with cocoapods, ruby, gradle, etc etc? What if most of your team didn’t even need to install Xcode/Studio at all? I believe this strategy can be beneficial for keeping you and your team productive, and isolate all the pain of the native integration to the CI builds.

So, how to get to this point? Some thoughts:

  • When considering libraries, ask yourself ‘is this pure-js or native?’. For instance, when evaluating options for a feature flag library, you could use FooFlags (not a real product) or LaunchDarkly. FooFlags has a react native library that wraps native code, however LaunchDarkly is a pure-js library. You should use the one that has a pure JS library, because that gets you one step closer to being able to do your daily work in Expo Go.

  • Sometimes, companies release newer versions of their libraries that are pure JS. LaunchDarkly did this in the last year or two: their older library was native + JS shim, but their newer one is pure JS. In cases like these, you can upgrade to the latest pure JS one to make your life easier.

  • If you have an unavoidably native component, you can wrap it in a pure-JS component that shows a placeholder. If this is a part of the app that you don’t need to work on very often, this can be a great way of having your cake and eating it too: Have native components for part of the app, yet still be able to spend most of your productive workday zipping along with Expo Go.

  • If you have native modules, you can shim them to perform no-ops (or whatever is reasonable) when in Expo Go. I’ll demonstrate some strategies for achieving these last 2 points next:

Your native expo modules

If you’ve made your own native module, you can ‘shim’ it out in such a way that it does nothing when run in the Expo Go environment. To do so, as an example, I modify the generated modules/my-foo-module/src/MyFooModule.ts file as follows:

import { NativeModule, requireNativeModule } from 'expo';
import Constants, { ExecutionEnvironment } from "expo-constants";
import { EventSubscription } from 'expo-modules-core';
import { MyFooModuleEvents } from './MyFooModule.types';
declare class MyFooModule extends NativeModule<MyFooModuleEvents> {
    PI: number;
    getValueSync(): string;
    setValueAsync(value: string): Promise<void>;
    doSomething(): void;
}
function requireOrMock(): MyFooModule {
    if (Constants.executionEnvironment ===
        ExecutionEnvironment.StoreClient) { // Expo Go:
        return {
            // My stuff:
            PI: 3.141,
            getValueSync: function (): string { return '' },
            setValueAsync: async function (value: string): Promise<void> {},
            doSomething: function (): void {},
            // Generic expo module stuff:
            addListener: function <EventName extends keyof MyFooModuleEvents>(eventName: EventName, listener: MyFooModuleEvents[EventName]): EventSubscription {
                return { remove: function(): void {} }
            },
            removeListener: function <EventName extends keyof MyFooModuleEvents>(eventName: EventName, listener: MyFooModuleEvents[EventName]): void {},
            removeAllListeners: function (eventName: keyof MyFooModuleEvents): void {},
            emit: function <EventName extends keyof MyFooModuleEvents>(eventName: EventName, ...args: Parameters<MyFooModuleEvents[EventName]>): void {},
            listenerCount: function <EventName extends keyof MyFooModuleEvents>(eventName: EventName): number { return 0 }
        } 
    } else {
        return requireNativeModule<MyFooModule>('MyFooModule');
    }
}
export default requireOrMock();

Imported library components

In our case, we use a native library for VOIP calling. We only have one component that uses this library, so I’ve added a ‘wrapper’ component that replaces our component with a placeholder when we’re using Expo Go. The wrapper works as follows:

import Constants, { ExecutionEnvironment } from "expo-constants";
import { Text, View } from "react-native";
import { MyComponentProps } from "./MyComponent";
// This wraps a MyComponent in such a way it is not instantiated for Expo Go.
export default function MyComponentWrapper(props: MyComponentProps) {
    if (Constants.executionEnvironment === ExecutionEnvironment.StoreClient) { // Expo Go:
        return (
            <View style=>
                <Text>This is disabled while using Expo Go</Text>
            </View>
        );
    } else { // Production:
        const { default: MyComponent } = require('./MyComponent'); // Lazy import.
        return <MyComponent {...props} ></MyComponent>
    }
}

This wrapper has the same props as the actual component, thus everywhere our component is used, this wrapper component is to be simply used instead.

For this to work, you have to edit MyComponent.tsx and export its props:

export interface MyComponentProps { ...

Summary

Hope you find this helpful! I strongly recommend using Expo Go for the sake of your team’s productivity if possible, and with the above tips, I think it is reasonably achievable. Thanks for reading, I pinky promise this was written by a human, not AI, hope you found this fascinating, at least a tiny bit, God bless!

Thanks for reading! And if you want to get in touch, I'd love to hear from you: chris.hulbert at gmail.

Chris Hulbert

(Comp Sci, Hons - UTS)

Software Developer (Freelancer / Contractor) in Australia.

I have worked at places such as Google, Cochlear, Assembly Payments, News Corp, Fox Sports, NineMSN, FetchTV, Coles, Woolworths, Trust Bank, and Westpac, among others. If you're looking for help developing an iOS app, drop me a line!

Get in touch:
[email protected]
github.com/chrishulbert
linkedin



 Subscribe via RSS