Server-side React with PHP – part 2

September 19th, 2013. Tagged: JavaScript, php, react

Part 1 ended with todos. The first one was to couple the server-side generated code with the client-side React, so that any updates past the initial page load will be handled by React's client JS, which is where React shines. Let's see how you can do just that.

PHP data fetch, gluing JS for v8

This part is the same as before, pasting here with no additional comments. The point is: you fetch the data with PHP somehow. You concatenate React with your app, pass the PHP data to the app and get ready to execute this JS with v8. (The concat stuff could, and should, be done by a build process actually, not at runtime, but this is just an illustration)

$react = array();
// stubs, react
$react[] = "var console = {warn: function(){}, error: print}";
$react[] = "var global = {}";
$react[] = file_get_contents('react/build/react.js');
$react[] = "var React = global.React";
// my custom components
$react[] = file_get_contents('react/build/table.js');
// my application, fetch the data
$data = array( // database, web services, whatevers
    array(1, 2, 3),
    array(4, 5, 6),
    array(7, 8, 9));
// my application, render the data
$react[] = sprintf(
  "React.renderComponentToString(Table({data: %s}), print)",
// concat all JS
$react = implode(";\n", $react);

Run JS and buffer

While before I just printed the result of the JS code (which is rendered HTML), here I want to keep it in a variable and use it later as part of a bigger template. So I execute the JS string but buffer the output to a variable. (I tried without a buffer, only using the return value of $v8->executeString() but couldn't make it happen, as React's renderComponentToString() is async and takes a callback)

$v8 = new V8Js();
try {
  // buffer v8 output to $markup
  $markup = ob_get_clean();
} catch (V8JsException $e) {
  // blow up spectacularly
  echo "<pre>"; var_dump($e); die();

Render a whole page

Finally, you take the rendered markup and replace a simple template which takes care of all the html/body/doctype, etc. Then you print it out.

// page template
$page = file_get_contents('page_template.html');
printf($page, $markup, json_encode($data));

In fact, this template will also take care of initializing React on the client.

The template/client

So what goes into this page_template.html? It takes care of the HTML boilerplate, load CSS, and so on. Then it puts all the server-rendered HTML in a div id=page. Finally it loads React.js and custom app .js (which could very well be concatenated into one). Once React is loaded you initialize the client-side, by passing the same $data used to render server-side.

<!doctype html>
    <title>React page</title>
    <!-- css and stuff -->
    <!-- render server content here -->
    <div id="page">%s</div> 
    <!-- load react and app code -->
    <script src="react/build/react.min.js"></script>
    <script src="react/build/table.js"></script>
    // client init/render
    var r = React.renderComponent(
      Table({data: %s}), 

So clearly there's a duplication of the data sent to the client: once rendered as HTML and once JSON-encoded. But the JSON encoded data should be very small in most cases. Alternatively you can always DOM-scrape the rendered HTML for the data and pass that back to React, but in most cases the scraping code will probably be longer than the JSON encoded thing.

Yet another strategy is to flush only partial data rendered as HTML, only as much (or as little) as needed to make the page appear responsive. Then in a second flush pass all the data as JSON and let React update the UI.


Rendered markup on the server:


Updating the data client-side to make sure client React is initialized and knows what it's doing


Here's a static version of the end result (can't install v8 on dreamhost's shared server) if you want to explore.


Thanks for reading, now go play with React. Meanwhile I'll try to clean this code up and setup a standalone project you can fork.

Tell your friends about this post on Facebook and Twitter

Sorry, comments disabled and hidden due to excessive spam.

Meanwhile, hit me up on twitter @stoyanstefanov