Building a live preview in React Native using your existing React components
— JavaScript, Software Development — 2 min read

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 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 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 HTML11 const htmlContent = ReactDOMServer.renderToString(<RenderInBoth name={name} whatUserDoes={activity} />);12 // Create a wrapper around the component's HTML to add stylesheet etc13 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 <WebView25 source={{ html: htmlWrapper }}26 originWhitelist={['*']}27 scalesPageToFit28 />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 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.