Eric Escalante Blogging gingerly

Isomorphic React apps in PHP via dnode - 4

Part 4 - Rendering from PHP with dnode

Or Ruby, or Java. Heck, even perl! Our proud little app can integrate with any language that has a dnode implementation!

When installing the php dnode package with composer, I was surprised to find installed a React dependency that actually has nothing to do with facebook’s! It seems to be a great library for evented i/o for php.

Also, when I first attempted this experiment, I tried using Facebook’s php library based on v8 or straight php v8 but it soon became obvious that another solution was needed.

In our node server

 1 dserver = dnode({
 2   renderIndex: function(remoteParams, remoteCallback) {
 3     var router = Router.create({
 4       location: '/developers/page/' + remoteParams.page,
 5       routes: routes
 6     });
 7     return router.run(function(Handler, state) {
 8       gutil.log("Serving to PHP via " + dnodePort);
 9       var component = state.routes[1].handler;
10       return component.fetchData(state.params, function(err, data) {
11         var reactOutput = React.renderToString(React.createElement(Handler, {
12           data: data,
13           params: state.params
14         }));
15         return remoteCallback(getHtml(reactOutput, data, state));
16       });
17     });
18   }
19 });
20 
21 dserver.listen(dnodePort);
22 
23 gutil.log("dnode: Listening on port " + dnodePort);

Explanation: These lines come after the declaration of our getHtml function in our server script (around line 48 over there). To begin things, we declare the name of the function we want to expose via dnode, in this case renderIndex. When the php side of dnode calls, it will provide the parameters and a remote function to be invoked as a callback once we’re done.

Inside our renderIndex function, on line 2, we’ll perform the now familiar task of getting a route handler for /developers/page/ (appending the page number sent by php). Then we go ahead and invoke the handler’s fetchData and use the resulting data to have React.renderToString provide the final html. Finally on line 18 we invoke the remote callback with the output of our handy getHtml function. Bam!

PHP index script.

 1 <!DOCTYPE html>
 2 <html lang="en">
 3   <head>
 4     <title>Sample PHP page rendering Node/ReactJS</title>
 5     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
 6   </head>
 7 
 8   <body style="background:#FAFAFA;padding:20px">
 9     <div class="container">
10       <div class="jumbotron">
11       <h1>Sample PHP page</h1>
12       <p>The content below is rendered server-side with PHP/node/react via dnode.</p>
13       <p>Subsequent pagination is done with react client-side via Ajax.</p>
14       <p>This could be a Drupal, Wordpress, or any other kind of PHP app :)</p>
15     </div>
16 
17     <div class="panel panel-default" style="padding:20px;">
18 <?php 
19   require 'vendor/autoload.php';
20 
21   $path =  explode('/', $_SERVER['REQUEST_URI']);
22 
23   $options  = array('page' => array_pop($path));
24   $loop = new React\EventLoop\StreamSelectLoop();
25   $dnode = new DNode\DNode($loop);
26   $dnode->connect(3001, function($remote, $connection) use($options) {
27     $remote->renderIndex($options, function($result) use ($connection) {
28         echo $result;
29         $connection->end();
30     });
31   });
32   $loop->run();
33 
34 ?>
35       </div>
36     </div>
37   </body>
38 </html>

Explanation: The markup is just a simple html page, but close your eyes for a second and picture this implemented properly on a large, living app.

The magic starts at line 26, where we tell our dnode php instance to connect to port 3001, the one we defined in our node server script as dnodePort = 3001. This gives us a $remote object from which we can make calls to functions implemented on the other side of this funny wormhole, back in node.

So, when we do $remote->renderIndex that renderIndex is the function we just defined on our server script a couple of paragraphs above! When I saw this running the first time my mind was totally blown (away)! - yes, I know I need to go out more.

Next, we just pass the current page inside an $options array and get back a $result variable containing the output of the node app, including a link to our bundle.js that includes the browser-side react-routing code!

Our PHP app now serves pre-rendered react components! Once the page is ready, all user interaction is handled by the react components client side, communicating via ajax to the node app. Note that we can send the response from our node app in array so we can have body and head elements separately (like open graph tags, for example).

We made it!

This ends this short series and I hope it was fun and useful for people learning node, gulp, react, and curious about remote procedure calls, etc.

Don’t forget to check out the repo and get in touch with suggestions, questions, or complaints about my cat sneaking into your balcony and causing mischief!

Isomorphic React apps in PHP via dnode - 3

Part 3 - Server and client-side rendering

Now the plot begins to thicken. It’s time to work a little bit on the backend, behind the courtains.

Node.js server

 1 var React = require('react');
 2 var Router = require('react-router');
 3 var routes = require('./routes');
 4 var dnode = require('dnode');
 5 var express = require('express');
 6 var app = express();
 7 var gutil = require('gulp-util');
 8 
 9 regularPort = 3000;
10 dnodePort = 3001;
11 
12 app.get('/developers/page/*', function(req, res) {
13   var router = Router.create({
14     location: req.url,
15     routes: routes
16   });
17   return router.run(function(Handler, state) {
18     gutil.log("Serving to browser via " + regularPort);
19     var component = state.routes[1].handler;
20     return component.fetchData(state.params, function(err, data) {
21       var reactOutput = React.renderToString(React.createElement(Handler, {
22         data: data,
23         params: state.params
24       }));
25       return res.send(getHtml(reactOutput, data, state));
26     });
27   });
28 });
29 
30 app.use(express["static"](__dirname + '/public'));
31 
32 server = app.listen(regularPort, function() {
33   return gutil.log("HTTP: Listening on port " + regularPort);
34 });
35 
36 getHtml = function(reactOutput, data, state) {
37   var response;
38   response = '<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">';
39   response += "<script>window.reactData = " + (JSON.stringify(data)) + "</script>";
40   response += "<script>window.initialPage = " + state.params.page + "</script>";
41   response += '<div id="app" class="container">';
42   response += reactOutput;
43   response += '</div>';
44   return response += "<script src='http://localhost:" + regularPort + "/js/bundle.js'></script>";
45 };

Feeble attempt at an explanation: Ok, let’s take this one step at a time. We start first by including our routes definition and setting up a simple Express app. Finally we declare two variables that will tell node and dnode on which ports they should listen for connections.

On our Express app instance, we will define a function to handle GET requests to /developers/page/*. This function returns a react-router instance created using the current req.url and our routes configuration (line 12). Next we invoke the run function on our router instance and obtain the react component that will handle the route and the current route state (params).

On line 22 we invoke the static function we coded into our DeveloperList component (FetchData, remember?) and pass a callback function to act upon the resulting data from the request to Github’s API.

On line 24 we invoke React’s renderToString method passing our DeveloperList component (represented by the Handler variable), the resulting data from fetchData, and finally the state.params provided by react router (page: 1 in this case). The resulting HTML we pass to a custom function called getHtml that will set the data as JSON object in the page so react can use it the first time. This function also includes bootstrap and more importantly, the bundle.js file generated by browserify. All this unwholesome HTML concatenatin’ can be replaced with a jade template for example, but just wanted to keep it all server stuff on a single file for this post :)

We wrap things up on line 28 by sending the output of our getHtml function as the response from our express app back to the browser.

Browser logic

We now have a working server and router to handle the initial request of a user. But we also need to configure our router to handle requests done once the page has been served, and do this without bothering the server script. We need now a browser script!

 1 var React = require('react');
 2 var Router = require('react-router');
 3 var routes = require('./routes');
 4 
 5 Router.run(routes, Router.HistoryLocation, function(Handler, state) {
 6   if (window.initialPage === parseInt(state.params.page)) {
 7     return React.render(
 8       <Handler data={window.reactData} params={state.params}/>, document.getElementById('app')
 9     );
10   } else {
11     var component = state.routes[1].handler;
12     return component.fetchData(state.params, function(err, data) {
13       React.render(
14       <Handler data={data} params={state.params}/>, document.getElementById('app')
15       );
16     });
17   }
18 });

Explanation: Thanks Cthulhu and all the Great Old Ones, things get more simple from now on. Ish. The first three lines should look familiar by now, so we can move along. On line 5 we create once again a router instance but this time we pass the browser url provided by Router.HistoryLocation together with our routes definition.

We need to tell react to render the resulting Handler (our trusty developers list) using either the JSON data that comes already hardcoded on the page thanks to node, or to do a new call to Github’s API, in order to avoid doing an unnecessary request the first time. That little if on line 6 makes sure of just that. So, when it’s the first render of the page, we just invoke React.render with the data present, otherwise we make a call to the fetchData function of our Handler and use the result of that as the data to be rendered. I still feel there must be a better way to handle this scenario. Looking forward to improving on that!

That’s it! We have a this point an isomorphic/universal/$nextTrendyName node app, that makes use of the same react components on both the server and the client! It’s Sunday and it’s sunny outside, so time to head out and walk a bit in the sun. But first, let’s hook all of this up to PHP.

Because you only live once.

Isomorphic React apps in PHP via dnode - 2

Part 2 - Route configuration

Now that we have coded our components, we need to link them up to a route handler, so when a user goes to http://localhost:3000/developers/page/1 we can catch that page 1 parameter and pass it as a property to our developers list.

Route configuration

1 var React = require('react');
2 var Route = require('react-router').Route;
3 
4 module.exports = [
5   <Route>
6     <Route name='index' path="/developers/page/:page" handler={require('./developer_list.js')} ignoreScrollBehavior/>
7   </Route>
8 ];

Explanation: At the top we declare our React dependency and then go ahead and create an instance of react-router. The next step is to export a route declaration, in which we declare that the path /developers/page/:page will be handled by the developers_list.js component.

The ignoreScrollBehavior is a handy little parameter that prevents the browser from scrolling up when a user clicks on our pagination links. The page paramater will later tickle down via state.params.page to the props object of our react components.

As a nice bonus of using React Router we have managed to create three state-less components. It’s considered a best practice to keep as little state as possible in your components, since every change of state triggers a new rendering of the component. As you can see, none of the components implement a getInitialState function and only work with the props handed down to them from above.

Later on, we’ll include this route definition script in our server script and browser script files in order to process urls coming via http requests and browser locations respectively.

Isomorphic React apps in PHP via dnode - 1

Part 1 - React components

In this section we’ll cover the react components, please make sure you have checked out the sample repo so you can run the code as you read.

The application will implement three react components: a developer list, a mini profile and a pager.

MiniProfile

 1 var React = require('react');
 2 
 3 var MiniProfile = React.createClass({
 4   displayName: 'MiniProfile',
 5   render: function() {
 6     var user = this.props.user;
 7     var css = {width:120, padding:10};
 8     return (
 9       <div className="pull-left">
10         <img src={user.avatar_url} style={css} className="img-circle pull-left"/>
11         <div className="text-center">
12           <a href={user.html_url} target="_blank">
13             <span className="label label-info">{user.login}</span>
14           </a>
15         </div>
16       </div>
17       );
18     }
19 });
20 
21 module.exports = MiniProfile;

Explanation: This component has only one dependency, so we declare a React instance on line 1. Using the react instance we proceed to create our component with React.createClass and give it the name MiniProfile. The displayName property allows the name of our component to be shown on the React plugin for chrome :) Next, our component implements the render method, with the user received as a property via this.props.user and lovely JSX. Finally, we export our MiniProfile so it’s available for inclusion on other scripts, as we’ll see on the developer list.

Pagination

 1 var React = require('react');
 2 var cx = require('classnames');
 3 var Link = require('react-router').Link;
 4 
 5 var Pagination = React.createClass({
 6   displayName: 'Pagination',
 7   render: function() {
 8     var linkCss = {marginTop: 20};
 9     var buttonCss = {margin: 5};
10     var prevPage = {page: this.props.currentPage - 1};
11     var nextPage = {page: this.props.currentPage + 1};
12     var prevClasses = cx({
13       'glyphicon': true,
14       'glyphicon-chevron-left': true,
15       'btn btn-primary': true,
16       'hidden': this.props.currentPage === 1
17     });
18     return (
19       <div className="row clearfix text-center" style={linkCss}>
20         <Link to="index" params={prevPage} style={buttonCss}>
21           <button className={prevClasses}>
22           </button>
23         </Link>
24         <Link to="index" params={nextPage} style={buttonCss}>
25           <button className="glyphicon glyphicon-chevron-right btn btn-primary">
26           </button>
27         </Link>
28       </div>
29     );
30   }
31 });

Explanation This component has two additional dependencies beside just React. On line 2 we create an instance of classnames used to add css classnames based on a condition (not showing the left chevron on the first page in this case). The second dependency is for Link a component that comes with react-router. Link will help us creating links that allow updating the browser’s url while navigating through our developer list via ajax and also work as traditional anchor tags when javascript is not available, as in the case of a search robot or a clunky browser. The JSX bit just adds a ‘previous’ and ‘next’ chevrons so we can move back and forth in the list.

Developer list

 1 var React = require('react');
 2 var request = require('request');
 3 var MiniProfile = require('./miniprofile');
 4 var Pagination = require('./pagination');
 5 
 6 var DeveloperList = React.createClass({
 7   displayName: 'DeveloperList',
 8   statics: {
 9     fetchData: function(params, callback) {
10       'Fetching from Github';
11       var options = {
12         url: "https://api.github.com/users?since=" + (params.page * 30),
13         headers: {
14           'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0'
15         },
16         withCredentials: false
17       };
18       return request(options, function(error, response, body) {
19         callback(null, JSON.parse(body));
20       });
21     }
22   },
23   render: function() {
24     var profiles = []
25     this.props.data.forEach(function(user, index) {
26       profiles.push(<MiniProfile key={index} user={user}/>);
27     });
28       
29     return (
30       <div>
31         <div className="row">
32           {profiles}
33         </div>
34         <Pagination currentPage={parseInt(this.props.params.page)}/>
35       </div>
36     );
37   }
38 });
39 
40 module.exports = DeveloperList;

Explanation: It all comes together on our developer list component. Lines 1 - 4 define the dependencies for this component, starting with React. Then we create a request instance, which we’ll use later on to make our call to Github’s API. The last to lines create instances of our previously defined MiniProfile and Pagination components. Of note here, is that we have created our data fetching function as a static, in order to be able to call it later on from React Router. The data resulting from this function will be passed by the router as a property of our developer list, so we can make use of it in line 26 in a forEach as a source to build individual MiniProfiles for each developer in our render function. To finish things off, we include our Pagination component and pass the current page as a property.

That’s it for the first part, on the next post we’ll go over the process hooking our developer list component to a router and making it work both on the server and the browser!

Isomorphic React apps in PHP via dnode

Introduction

In this four-part series, we’re going to cover how to go about making a PHP app benefit from the use of ReactJS components both server and client-side. The case for this is, for example, if we have a Wordpress or Drupal CMS we want to keep around for it’s content management functions, but want to extend with new and modern functionality.

Being able share some of the javascript code both on the server and client provides many advantages, such as:

  • SEO: Search engines should be able to crawl and index our content. No need to implement/pay for a pre-rendering service to achieve this.
  • Performance: A pre-rendered page can be cached server-side and provide a snappy user experience. React hooks itself up once the page is served and takes over the rendering of it’s components based on user interactions.
  • Compatibility: A browser with no javascript enabled can still display the page and allow for a simple navigation.
  • Usability: A user is be able to bookmark the current state of the page and is able to return to it or share it.
  • Maintainability: Sharing code between the server and the browser allows for a smaller code base, which is easier to keep healthy.

For these reasons, just adding React components to the existing user interface of our CMS would prevent us from reaping the full benefits, even though it would still provide our application with the reduced development times, maintainability and faster response time that React brings to the table.

Tools used

This post assumes some basic familiarity with the following:

Edit: [07/04/2015] Replaced coffeescript snippets with regular javascript (ES5) based on feedback from my good friend Luke :)

Sample application

The goal of our brave little sample app is to connect to Github’s API and fetch a list of developers and display their username and avatar. We’ll also provide a link to their Github profile page and show a simple back and forth pager at the bottom.

The series will be spread as follows:

  • Intro
  • Part 1: React components
  • Part 2: Router configuration
  • Part 3: Server-side rendering
  • Part 4: Rendering from PHP with dnode

I’ve created a repository with the finished app so you can download, analyze and tinker with it as you read along. Please be gentle, it’s my first attempt at a coding post+repo! You can find it at: ericescalante/isomorphic-post-code