Skip to content
Colin Wren
Twitter

Building a live preview in React Native using your existing React components

JavaScript, Software Development2 min read

tech equipment
Photo by Fabian Grohs on Unsplash

I found myself recently converting a React based website which gives the user a live preview of a document they’re building while allowing them to change the content and styling of the document with ease before printing.

The preview worked really well as you could quickly style the document in a way you like and convert that preview to a PDF, then send that document off to who you need to, a huge improvement over doing that type of work in Word.

With the conversion from a React website to a React Native app I wanted to keep the preview and if possible re-use the same components used in that preview instead of porting the multitude of templates to React Native.

This approach, while tricky would allow the app to take advantage of all the templates used on the web version, as well as any additional templates that get added over time.

For my use case this works very well and means I can write one set of components for laying out a document and render it across web, app and print which makes supporting multiple platforms so much easier.

Example React Component

In order to illustrate the process I’ll create an example React component. This component would be used in a React website to layout some information the user has provided, something we want to re-use in our React Native app.

1import React from 'react';
2
3export default function RenderInBoth({ name, whatUserDoes, children }) {
4 return (
5 <main className="flex-grow">
6 <section className="px-6 border-b-2 border-gray-200">
7 <div className="container mx-auto min-h-screen flex justify-center items-center">
8 <div class="w-full sm:w-11/12">
9 <div>
10 <h1 className="text-5xl text-gray-900 leading-tight font-medium">Hi my name is {name}</h1>
11 <h2 className="text-xl text-gray-700 mb-6">And I {whatUserDoes}</h2>
12 </div>
13 <div class="mb-20">
14 {children}
15 </div>
16 </div>
17 </div>
18 </section>
19 </main>
20 );
21}
This component simply returns the name and activity passed in and renders children (for things like inputs)

This component should live in some form of component library which can be imported into the React website, this is important as if this component is stored locally then React and React Native would have issues finding the file to import it.

1import React, { useState } from 'react';
2import RenderInBoth from '@company/component-library';
3
4function App(){
5 const [name, setName] = useState();
6 const [activity, setActivity] = useState();
7 return (
8 <RenderInBoth name={name} whatUserDoes={activtiy}>
9 <div class="flex flex-wrap -mx-3 mb-6">
10 <div class="w-full md:w-1/2 px-3 mb-6 md:mb-0">
11 <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" for="grid-name">
12 What's your name?
13 </label>
14 <input class="appearance-none block w-full bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" id="grid-name" type="text" value={name} onChange={(e) => setName(e.target.value)} />
15 </div>
16 <div class="w-full md:w-1/2 px-3">
17 <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" for="grid-last-name">
18 What do you do?
19 </label>
20 <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" id="grid-activity" type="text" value={activity} onChange={(e) => setActivity(e.target.value)} />
21 </div>
22 </div>
23 </RenderInBoth>
24 );
25}
Rendering the component in React, passing in a form as a child so it will render the form inside the layout

Rendering React in React Native

The Webview component from react-native-webview can render a HTML string that’s passed to it via props so we’ll use this to show the component, however we can’t just pass it the component and hope it figures it out we need to convert it to HTML first.

The react-dom/server module has a function called renderToString which takes a React component and returns a HTML string, but as the component we’re working with was intended to render a sub tree in a web page it was missing the CSS definitions it was marked up to use.

1import React, { useState } from 'react';
2import { SafeAreaView, View, Dimensions, TextInput, Text } from 'react-native';
3import { WebView } from 'react-native-webview';
4import ReactDOMServer from 'react-dom/server';
5import RenderInBoth from '@company/component-library';
6
7export default function App() {
8 const [name, setName] = useState();
9 const [activity, setActivity] = useState();
10 // Render component to HTML
11 const htmlContent = ReactDOMServer.renderToString(<RenderInBoth name={name} whatUserDoes={activity} />);
12 // Create a wrapper around the component's HTML to add stylesheet etc
13 const htmlWrapper = `<html><head><link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" /></head><body>${htmlContent}</body></html>`;
14 const { width } = Dimensions.get('window');
15 return (
16 <SafeAreaView>
17 <View style={{ flexDirection: 'column' }}>
18 <View style={{ alignSelf: 'stretch', padding: 20 }}>
19 <View style={{
20 width: (width - 40),
21 height: 400,
22 elevation: 5,
23 }}>
24 <WebView
25 source={{ html: htmlWrapper }}
26 originWhitelist={['*']}
27 scalesPageToFit
28 />
29 </View>
30 </View>
31 <View style={{ padding: 20 }}>
32 <Text>What is your name?</Text>
33 <TextInput style={{ height: 40, borderColor: 'gray', borderWidth: 1 }} value={name} onChangeText={text => setName(text)} />
34 <Text>What do you do?</Text>
35 <TextInput style={{ height: 40, borderColor: 'gray', borderWidth: 1 }} value={activity} onChangeText={text => setActivity(text)} />
36 </View>
37 </View>
38 </SafeAreaView>
39 );
40}
You’ll need to install react-native-webview and react-dom in order to use this example

You can wrap the HTML returned from the renderToString function in a HTML skeleton that adds the markup to import the CSS framework and adds the originWhitelist prop to allow the Webview to download these resources the mark up is using.

The end result is a live preview of the component’s output in a Webview that is controllable with React Native components such as TextInput .

Caveats

It’s worth pointing out that the use case for what I was doing doesn’t require real-time updates so I can’t verify that the performance of such a technique is for applications that need instantaneous updates.

Additionally, if you’re working with connected components then it’s unlikely this approach will work, however if you refactor the component to separate state and structure then you should be able to use this approach.