Now that our app is in place we can start introducing server side rendering for React.

This requires one additional component we will have to download into our statics/js directory, the react-dom-server library which allows server side rendering of React

$ cd statics/js $ curl -O 'https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-dom-server.js'

Then we are going to render our component into our Python web application, to do so we are going to rely on DukPy for the actual rendering and WebAssets for providing the required dependencies

import json

from dukpy import JSInterpreter, jsx_compile

from markupsafe import Markup





class ReactRenderer(object):

def __init__(self, jspath):

self.jspath = jspath

self.jsi = JSInterpreter()

self.jsi.loader.register_path(self.jspath)

self.components = {}

self.initialized = False



def _init(self):

if self.initialized:

return



bundle_js = tg.app_globals.webassets['bundle.js']

self.jsi.evaljs(

[f.data() for f in bundle_js.build()] +

["var ReactDOM = require('react-dom-server');"]

)

self.initialized = True



def render(self, component, **kwargs):

self._init()

code = "ReactDOM.renderToString(React.createElement({component}, {args}), null);".format(component=component, args=json.dumps(kwargs))

return Markup(self.jsi.evaljs(code))

The ReactRenderer is a convenience class that will create a DukPy interpreter with React and our HelloWorld component preloaded (through the bundle.js WebAssets bundle we already declared) and react-dom-server loaded through require

In fact the class consists of a single render() method which will initialise the interpreter (if it’s not already initialised) and will then render the specified React Component. So we can use this class to render any Component that was available into our bundle.js including the HelloWorld one.

Only part left is we need to create it and provide it to our index() action so that it can use it to render the component. For convenience and as usually I’ll need the ReactRenderer object available everywhere I’ll make it available into the configuration of my application

import os config.react_renderer = ReactRenderer(

os.path.join(os.path.dirname(__file__), 'statics', 'js')

)

Make sure you add this line before creating the TurboGears application (so before make_wsgi_app). The argument provided to ReactRenderer is the path where it can find any additional javascript module that will be loaded through require, in this case as we downloaded react-dom-server in statics/js that’s the specified path.

Now that our ReactRenderer is in place we can edit our index action and provide the react renderer to our HTML template





def index(self):

return page(dict(

render_react=tg.config['react_renderer'].render,

g=tg.app_globals

)).render() class RootController(TGController): @expose ()def index(self):return page(dict(g=tg.app_globals)).render()

If you properly added the render_react value to the ones the controller action provides to the page template we can now change the template itself to render the component.

If you remember we previously had an empty isomor div

<div id="isomor"></div>

that div acted only as a target for our ReactDOM.render call which rendered the component and placed it into the div.

This was pretty clear through the fact that our page when loaded was empty for a moment and then the content appeared a little later when React was able to render it.

What we are going to do is replacing that empty div with one with the component pre-rendered inside:

<div id="isomor">${render_react('HelloWorld.HelloWorld', name='World')}</div>

The render_react callable is in fact the ReactRender.render method we provided from the action. If you remember the first argument is the Component that should be rendered (in this case HelloWorld from the HelloWorld module) and any additional keyword argument is passed as a property of the component. In this case we are providing the name=World property (same as we did in the React.createElement call).

Note that it is really important that any property provided to the Component when rendering it from python matches those provided to the React.createElement call in JS or React will complain and will replace the div content instead of reusing it (same will happen if you wrongly put empty spaces before or after the rendered component).