Rendering React with PHP, part two: semantic response codes

If you've read my last article, you now know how to render React code in a PHP app in order to serve the same content for the same URL regardless of how we navigate to it (hitting the server or interacting in the client). Today, we are going to build upon last time's example in order to make our Silex app yield semantic HTTP response codes based on what react-router says: 302s for redirects, 404s for inexistant URLs and 500s for errors.

Let's start by modifying our PHP code:

<?php

use Silex\Application;
use Silex\Provider\TwigServiceProvider;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

require_once(__DIR__.'/vendor/autoload.php');

$app = new Application();
$app['v8js'] = new V8Js();

$app->register(new TwigServiceProvider(), ['twig.path' => __DIR__.'/views']);

$app
->get(
'/{uri}',
function ($uri) use ($app) {
// Define content markers for special cases
$redirectPrefix = 'redirect=';
$notFound = 'notfound';
$errorPrefix = 'error=';

$js = implode(';', [
'var uri = \'/'.$uri.'\'',
// Inject them to be able to use them on the JS side
'var redirectPrefix = \''.$redirectPrefix.'\'',
'var notFound = \''.$notFound.'\'',
'var errorPrefix = \''.$errorPrefix.'\'',
file_get_contents(__DIR__.'/dist/app.js')
]);

ob_start();
$app['v8js']->executeString($js);
$content = ob_get_clean();

if (preg_match('/^'.$redirectPrefix.'/', $content)) {
// JS-generated response is "redirect=/new/uri"
return new RedirectResponse(substr($content, strlen($redirectPrefix)));
}

if ($notFound === $content) {
throw new NotFoundHttpException();
}

if (preg_match('/^'.$errorPrefix.'/', $content)) {
// JS-generated response is "error=Some error message"
throw new RuntimeException(substr($content, strlen($errorPrefix)));
}

return $app['twig']->render('app.twig', compact('content'));
}
)
->assert('uri', '.*')
->value('uri', '');

$app->run();

We will now make our JavaScript code craft appropriately shaped responses to make it all work together:

import React from 'react';
import ReactDOM from 'react-dom';
import { renderToString } from 'react-dom/server';
import { match, RoutingContext } from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import routes from './routes';

(function() {
'use strict';

if ('undefined' !== typeof document) {
// client-side
// ...
} else {
match(
{
routes,
location: uri
},
function(error, redirectLocation, renderProps) {
print(error
? errorPrefix + JSON.stringify(error)
: redirectLocation
? redirectPrefix + redirectLocation.pathname + redirectLocation.search
: renderProps
? renderToString(<RoutingContext {...renderProps} />)
: notFound
);
}
);
}
})();

And it works! To try it out, you can add a redirect to your routes as follows:

import Root from './components/root';
import HomePage from './components/homePage';
import OtherPage from './components/otherPage';

export default {
path: '/',
component: Root,
indexRoute: {
component: HomePage
},
childRoutes: [
{
path: 'other',
component: OtherPage
},
{
path: 'redirect',
onEnter: function (nextState, replaceState) {
replaceState(null, '/');
}
}
]
};

You obviously also can use react-router's JSX notation, if you prefer to do so.

Routing-aware React server-side rendering on PHP with Silex and react-router

Not everybody can use Node.js on the server side to be able to craft truly universal JavaScript applications. Still, one might want to set up server-side rendering to enhance React's performances, and of course allow JS-uncapable clients to use their app seamlessly (at least regarding navigation).

In , I quickly demonstrated how to render React code with PHP's v8js extension. We will now see how to handle the routing with Silex and react-router.

Note: this article's snippets are written using ES6.

First, let's define our routes:

import Root from './components/root';
import HomePage from './components/homePage';
import OtherPage from './components/otherPage';

export default {
path: '/',
component: Root,
indexRoute: {
component: HomePage
},
childRoutes: [
{
path: 'other',
component: OtherPage
}
]
};

Here are the components:

import React from 'react';

export default class Root extends React.Component
{
render()
{
return (
<div>{this.props.children}</div>
);
}
}

import React from 'react';

export default class HomePage extends React.Component
{
render()
{
return (
<div>Hello world!</div>
);
}
}

import React from 'react';

export default class OtherPage extends React.Component
{
render()
{
return (
<div>Hi there! I am another page.</div>
);
}
}

We add the entry point (typically, app.js) to the mix:

import React from 'react';
import ReactDOM from 'react-dom';
import { renderToString } from 'react-dom/server';
import { match, RoutingContext } from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import routes from './routes';

(function() {
'use strict';

if ('undefined' !== typeof document) {
// We're on the client, nothing new here
ReactDOM.render(
<Router
routes={routes}
history={createBrowserHistory({ queryKey: false })}
/>,
document.getElementById('app') // don't use document.body
);
} else {
// We're on the server, this will be executed by v8js
match(
{
routes,
location: uri
},
function(error, redirectLocation, renderProps) {
print(error
? error.message
: renderToString(<RoutingContext {...renderProps} />)
);
}
);
}
})();

You can see I used the same hack as in my previous article to tell apart client and server-side rendering routines. You can also see that on the server, the matched location used for routing comes from a variable named uri, that wasn't declared anywhere. To understand this, we must jump to index.php:

<?php

use Silex\Application;
use Silex\Provider\TwigServiceProvider;

require_once(__DIR__.'/vendor/autoload.php');

$app = new Application();
$app['v8js'] = new V8Js();

$app->register(new TwigServiceProvider(), ['twig.path' => __DIR__.'/views']);

$app
->get(
'/{uri}',
function ($uri) use ($app) {
$js = implode(';', [
'var uri = \'/'.$uri.'\'', // inject URI
file_get_contents(__DIR__.'/dist/app.js')
]);

ob_start();
$app['v8js']->executeString($js);
$content = ob_get_clean();

return $app['twig']->render('app.twig', compact('content'));
}
)
->assert('uri', '.*')
->value('uri', '');

$app->run();

We end with our Twig template:

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<title>Test</title>
</head>

<body>
<div id="app">{{ content|raw }}</div>
<!-- This script tag is the reason we needed
a container inside the body -->
<script src="dist/app.js"></script>
</body>

</html>

In order for this to work, your browser must support the History API, and you must use a web server such as Apache configured in the likes of this:

<IfModule mod_rewrite.c>
Options -MultiViews

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
</IfModule>

You can then try out server-side rendering by commenting out the script tag in your template, in order to notice your React code truly is rendered by PHP! Navigate to /other and you will see the alternative content show up.

Your app can now be browsed with and without JavaScript!

Managing a variable configuration for a Flux application with gulp

Once you're done with the development of your Flux application, it is time to deploy it to your staging and/or production environment. But what if the app relies on configuration values that need to be specific to these environments? We will see how to address such a concern with Gulp.

We are going to store our configuration in JSON files. Values will be defined as environment variables, which can be set upon packaging your app with a continuous integration tool, and will be applied by a good ol' Bash script with regard to default values used for development. I assume all files, including gulpfile.js, are located at the project's root. We will begin by writing a sample configuration file, named (for example) config.json.dist:

{
"myParam1": "this value is environment-agnostic",
"myParam2": "?MY_PARAM_2?"
}

Next, we need to copy this file to config.json, which we will then require in our JavaScript code (with the help of Browserify). This is where gulp comes into play:

var gulp = require('gulp'),
rename = require('gulp-rename');

gulp.task('config', function() {
gulp.src('config.json.dist')
.pipe(rename('config.json'))
.pipe(gulp.dest('.'));
});

Make sure to npm install gulp-rename --save-dev if necessary.

We then want the ?MY_PARAM_2? placeholder to be swapped for the actual value. Let's set up the envify script that does that:

#!/bin/bash

set -e
cd "`dirname "$0"`"

declare -A DEFAULTS

# Set up default configuration values
DEFAULTS["MY_PARAM_2"]="this one isn't"

# Push them as environment variables if they don't already exist
for key in "${!DEFAULTS[@]}"; do
if [ -z "${!key}" ]; then
declare -x "$key"="${DEFAULTS[$key]}"
fi
done

# Edit configuration files accordingly
sed -i "s,?MY_PARAM_2?,$MY_PARAM_2,g" config.json

Have gulp run this script after copying the sample file:

gulp.task('envify', ['config'], function() {
gulp.src('')
.pipe(tasks.shell(['chmod +x envify', 'envify']));
});

Note: the ['config'] thing should make gulp run the config task prior to this one; however, I sometimes have encountered race conditions forcing me to make the shell sleep before actually running the script. It's a dirty hack though, so if you have had the same issue and found a better way to work around it, I'm interested!

After running gulp envify, you should see a config.json file with the following contents:

{
"myParam1": "this value is environment-agnostic",
"myParam2": "this one isn't"
}

If you do it again after running export MY_PARAM_2="I love bananas", the file will look like this instead:

{
"myParam1": "this value is environment-agnostic",
"myParam2": "I love bananas"
}

You can thus do the same in your CI job, and that's how you get environment-specific configuration in a Flux application. Stay tuned!

I would like to seize this opportunity to say that all my affection goes to victims of terrorism, from Paris last friday to other places in the world where such atrocities happen frequently, such as Beirut. Remember freedom and love cannot be killed; stay strong together and support each other in the hope of brighter days.

Handle events outside of your React components with Flux

Flux is an architecture methodology for React applications proposed by Facebook to support its own UI library. It is designed to help inject data from the server (or any other data source) into the React components and the other way around.

React components are pieces of knowledge that should deal with themselves and nothing else, to respect the separation of concerns principle that comes with the object-oriented programming paradigm. When using such an architecture, one might wonder how to handle JavaScript events happening outside of a component's scope, but still affecting it; a good example could be a dropdown menu that needs to be closed whenever the user clicks anywhere on the page, outside of it. Flux is actually able to answer such a problematic: let's figure out how!

Note: I will use a basic Flux implementation as described by Facebook.

First, let's define our Dropdown component:

'use strict';

var React = require('react'),
classNames = require('classnames');

module.exports = React.createClass({
getInitialState: function() {
return {
open: false
};
},

toggle: function() {
this.setState({
open: !this.state.open
});
},

render: function() {
return (
<div className={classNames({ open: this.state.open })} onClick={this.toggle}>
{this.props.children}
</div>
);
}
});

We need to catch clicks happening throughout our app (which we assume lives in a Root component) and let Dropdown know when they happen, so it can call its own toggle to close itself up. In order to do so, we will simply follow the Flux pattern, and start by defining a constant for the click event in a browserConstants module:

'use strict';

module.exports = {
ACTION_CLICK: 'ACTION_CLICK'
};

We will also set up the related action, in a browserActions module:

'use strict';

var browserConstants = require('../constants/browserConstants'),
dispatcher = require('../dispatcher');

module.exports = {
appClick: function() {
dispatcher.handleAction({
actionType: browserConstants.ACTION_CLICK
});
}
};

It is now time to implement our browserStore:

'use strict';

var EventEmitter2 = require('eventemitter2').EventEmitter2,
dispatcher = require('../dispatcher'),
browserConstants = require('../constants/browserConstants'),
store = {};

Object.assign(
store,
EventEmitter2.prototype,
{
emitClick: function() {
this.emit('click');
},

addClickListener: function(callback) {
this.addListener('click', callback);
},

removeClickListener: function(callback) {
this.removeListener('click', callback);
}
}
);

dispatcher.register(function(payload) {
switch (payload.action.actionType) {
case browserConstants.ACTION_CLICK:
store.emitClick();
break;
}

return true;
});

module.exports = store;

We are now able to dispatch the click event by calling an action, which is the next thing we will do in our Root component:

'use strict';

var React = require('react'),
browserActions = require('../actions/browserActions');

module.exports = React.createClass({
render: function() {
return (
<div onClick={browserActions.appClick}>
{/* your content here */}
</div>
);
}
});

The last thing to do is to listen and react to this event in Dropdown:

'use strict';

var React = require('react'),
browserStore = require('../stores/browserStore'),
classNames = require('classnames');

module.exports = React.createClass({
getInitialState: function() {
return {
open: false
};
},

toggle: function() {
this.setState({
open: !this.state.open
});
},

onAppClick: function() {
if (this.state.open) {
this.toggle();
}
},

componentDidMount: function() {
browserStore.addClickListener(this.onAppClick);
},

componentWillUnmount: function() {
browserStore.removeClickListener(this.onAppClick);
},

render: function() {
return (
<div className={classNames({ open: this.state.open })} onClick={this.toggle}>
{this.props.children}
</div>
);
}
});

Now, clicking anywhere will close any open instance of this component! Messing around with the Flux pattern in such a way might prove itself useful in other contexts, feel free to leave a comment if you have anything to share on the subject.

Does your JavaScript code still need IIFEs and explicit strict mode?

At a time when modules are becoming a de facto architectural solution for any serious frontend JavaScript codebase, and ES6 is at our doorstep, one may wonder if a systematic use of IIFEs and explicit strict mode declaration (using 'use strict';) is still relevant. This short note is thus designed to answer these questions.

Note: if you aren't using modules yet (should it be with CommonJS or the native ES6 syntax supported by Babel), you should really begin to! In both cases, Browserify is the way to go. Other solutions exist, of course, but these two respectively are the current (at least in the node.js world) and upcoming standard, and are the most broadly used.

Here we go:
  • A module's code does not need to be wrapped in an IIFE, as it is already in isolation anyway
  • 'use strict'; is still needed in CommonJS modules, nothing new here; on the contrary, it is implied in ES6 modules
  • 'use strict'; is needed in your code's entry point, which thus must also be wrapped in an IIFE to prevent concatenation side-effects

I hope this helped you know what you still have to do to keep your JS tidy as of today.