Choosing React Native comes with a set of unique advantages, such as a single ecosystem (JavaScript vs. Java/Swift/Objective-C), faster feedback loops and code reuse. All of the above makes for happy developers and, when a fully native user experience can be achieved, we can see why more teams choose to bring it on board.
At YLD, we use React on quite a few of our projects, and we started to wonder what was the best way to share code between native and web apps.
Problem and Solution
Reusing 100% of the codebase would be impossible at this stage, as React Native is oblivious to HTML elements like <div>
and <p>
, and uses its own native components instead - <View>
, <Text>
, and so on.
If conflicts arise within the render() function, we can overcome the problem by exporting into separate files; in short, sharing logic between "dumb" components is achieved by swapping the views.
To showcase our approach, we made a simple app that shows the latest articles from our blog as a list view. This has been a great exercise to see the “learn once write everywhere” mantra at work, as we maintained separate entry points to our app, but shared the same GraphQL server, (mock) database and most of the logic for our components.
The repository is online here and we would love some feedback!
Project Structure
The main entry points for the app are:
- app.js for native;
- client.js for the client;
- server.js for the server;
We chose Relay and GraphQL, and the web app is rendered both on the client and the server thanks to isomorphic-relay. The main src/ directory structure is very similar to a standard Relay isomorphic app, the only anomaly being the presence of two separate containers; this makes sense as we expect two different user experiences (native vs. web):
Hooking up React Native
React Native uses index.ios.js and index.android.js as entry points to compile the respective bundles. We import src/app.js in both of these, which look identical:
Keep views separate
The main challenge with this was adopting a flexible structure, allowing iOS and Android to share the same view (if needed), while keeping everything reasonably tidy. This is the structure of a simple Toolbar component that showcases different views for Android and iOS:
Every component lives in its own directory and exports a Component.js file, which in turns delegates the responsibility of displaying the view to a render() function. Props (including styles) are passed along from the main container down to the Render files:
React Native has this neat feature that allows you include iOS and Android specific files, without specifying the full path while importing.
We can take advantage of this by importing a “neutral” ./Render file; React Native will only see Render.android.js and Render.ios.js, which in turn will not be included in webpack's bundle for the web. React Native can also work with the .native.js extension, which we are using when iOS and Android share the same view:
Server-side rendering with Relay
Relay has a full working example in React Native as a TodoMVC app here. Our implementation is not far off, except we are using the isomorphic-relay library to help with server-side rendering.
This is only a proof of concept and the aesthetics leave a lot to be desired; however, we are relatively happy with what we have achieved given only a little experience of Android and iOS native development.
A few other projects aim to bridge the gap between native and web, and you can read more about it here. Among these, React Native Web has been mentioned by Eric Vicenti at React Europe, while showcasing a fully isomorphic app. As libraries like these become less experimental, hopefully this will become the way forward.