Introduction to React Native Renderers aka React Native is the Java and React Native Renderers are the JVMs of declarative UI

In the previous articles, we saw some introduction to react custom renderers and implementing a hello world custom react renderer. In this post, we are going to see what is different about implementing a custom renderer for React Native?

Does anyone remember the wave the Java brought about with its platform independence?

Write Once, Run Anywhere

You could write Java code on windows, and the same code can run on Linux or Mac or vice versa. This was possible because of JVM (Java Virtual Machine) implementations for different platforms. JVM takes the bytecode (intermdiate machine code generated by compiling Java source) and interprets/executes on the target platform.

Similarly, If you have a react native renderer for a particular platform, you can “run” the same react native app on different platforms. A React native renderer takes the React Native Component and maps it to a native UI element provided by the target platform.

Every UI framework has some basic set of UI elements like

  1. View or Container or ViewGroup that is used group/layout its child UI elements
  2. Text element to render text
  3. Image element to render image
  4. ScrollView element to scroll elements
  5. and some more

Each platform has a different way to layout, different way to display Text, different way to display Image but more or less all the platforms provide same functionality.

The only difference is the API between these platforms.

What if all of these can be abstracted into an uniform API?

Well that is what you can think React Native does.

React Native defines a set of component APIs that are platform agnostic and as long as the renderer for a specific platform provides an implementation for those components, the App will be able to run on that platform. Unlike in a typical React Custom Renderer where the component API is usually the declarative version of the target imperative API. There is no predefined set of components.

Officially, the React Native project provides renderers for iOS and Android but there are many community open source projects that provides react native renderers for Web, Windows, MacOS, Ubuntu etc.

As an app developer, one sure wants the app to run on as many platforms as possible with as little effort as possible, reusing the same code and without sacrificing performance. This is where React Native fits the bill. Unlike web/hybrid frameworks like Cordova or Ionic it doesn’t run the app in a web view rather, react native components map to actual native UI elements provided by the platform.

Let us look at implementing a simple react native renderer. We are going to build 2 different renderers one for Web and another for desktop using Yue: A library for creating native cross-platform (macOS, windows, Linux) GUI apps. The first few steps are very similar to hello-react-custom-renderer

  1. Create a new react project using create-react-app and start it.
create-react-app hello-react-native-custom-renderer
cd hello-react-native-custom-renderer
yarn start

Let us replace the ReactDomRenderer with our custom react native renderer in the index.jsfile.

import React from 'react';
import MyReactNativeWebRenderer from './myReactNativeWebRenderer';
import App from './App';
MyReactNativeWebRenderer.render(<App />, document.getElementById('root'));

2. Let us modify App.js as shown below.

We have simplified the UI as compared to hello-react-custom-renderer. This will serve as a test for this renderer implementation. It consists of a View and a centered Text component.

View: From React Native docs, View is “The most fundamental component for building a UI, View is a container that supports layout with flexbox, style, some touch handling, and accessibility controls. View maps directly to the native view equivalent on whatever platform React Native is running on, whether that is a UIView, <div>, android.view, etc.”

Text: “A React component for displaying text.”

3. Add react-reconciler package to the project. yarn add react-reconciler

4. Create a new file myReactNativeWebRenderer.js and copy the renderer source code from our react custom renderer.

5. We will modify the above renderer for our test case in App.js. We need to create 2 components i.e View and Text that maps to its target equivalents.

Side Note: “When an element type starts with a lowercase letter, it refers to a built-in component like <div> or <span> and results in a string 'div' or 'span' passed to React.createElement. Types that start with a capital letter like <Foo /> compile to React.createElement(Foo) and correspond to a component defined or imported in your JavaScript file.” You can read more at https://reactjs.org/docs/jsx-in-depth.html#user-defined-components-must-be-capitalized

We create View and Text as thin wrappers around lowercase view and text.

View: The complicated thing to implement in View is the flexbox layout. but since our target is web, it already has an implementation of flexbox.

The default flex layout direction for View component is column.

Text: Like the default React Native renderer and unlike our custom react renderer, we will render text in separate Text Components only. We do not need an explicit target UI element to render text on the web, but we will force strings to be rendered only in the Text component like react-native to align with standard react-native behaviour.

class Text extends Component {
render() {
return <text>{this.props.children}</text>
}
}

6. Let us see what are the corresponding changes in hostConfig.

shouldSetTextContent: (type, props) => {
return false;
},

We return false here because we want strings to be rendered in Text components only. This will make react-reconciler call createTextInstance

createTextInstance: (text, rootContainerInstance, hostContext, internalInstanceHandle) => {
return document.createTextNode(text);
},

We will also throw an error, if text is being used as children in non Text components in createInstance function.

Other things to notice here is that we are converting camel case style objects into inline styles, for simplicity of this demo. In ReactNative, the usual practice is to use the StyleSheet API

So, full myReactNativeWebRenderer.js looks as below.

And now , we should have a UI looking like this

7. Let us build the desktop UI react native renderer. The test case is the same App.js. The target is Yue GUI library. Add Yue library. The npm package name is gui

yard add gui

8. Create a new file myReactNativeYueRenderer.js and copy the code from myReactNativeWebRenderer.js

9. We will add new start file yue-index.js , babel.rc and some helper npm scripts

yue-index.js

import React from 'react';
import MyReactNativeYueRenderer from './myReactNativeYueRenderer';
import App from './App';
import gui from 'gui';
const win = gui.Window.create({});
win.setContentSize({ width: 400, height: 400 });
win.center();
win.activate();
MyReactNativeYueRenderer.render(<App />, win);
if (!process.versions.yode) {
gui.MessageLoop.run();
process.exit(0);
}

It is a bit different from index.js . We need to setup Yue gui window and the event loop. Check Yue docs for more details.

yarn add babel-cli babel-plugin-transform-object-spread babel-preset-env babel-preset-react -D

.babelrc

{
"presets": ["env", "react"],
"plugins": ["transform-object-rest-spread"]
}

package.json

"scripts": {
"start": "react-scripts start",
"start-yue": "./node_modules/.bin/babel-node --presets react ./src/yue-index.js",
.....
}

You should now be able to launch the app by running(but will not work)

yarn start-yue

11. The complete myReactNativeYueRenderer.js code is as below.

Very similar to myReactNativeWebRenderer.js but we are using Yue APIs instead of DOM APIs.

gui.Container.create() instead of document.createElement('div')

gui.Text.create(text) instead of document.createTextNode(text)

The Yue gui library similar to the web w.r.t layouting and has an implementation of flexbox layout powered by yoga-layout. We don’t even need to convert to inline styles as we have done in the web renderer. We just use the convenient setStyle method

.......} else if (propName === 'style') {
yueElement.setStyle(propValue);
}
......

We have the same application code running on 2 different targets ie. web and Yue GUI . The power of react native renderers.

https://twitter.com/agent_hunt/status/778746074949308420

Long time ago, I had tweeted “Where there is a need for UI, there shall be #reactnative”. This statement has become more evident with the list of react native renderers that are available today. Check out https://github.com/agenthunt/awesome-react-native-renderers

React Native if just not being used with a renderer implementation, there are ideas/projects around using react-native as a UI description language that can generate corresponding platform specific code.

The code for this post is at https://github.com/agenthunt/hello-react-native-custom-renderer

Solving Problems. Making Software Better. Intrigued by Elegant Solutions. https://twitter.com/agent_hunt