DEVELOPMENT by Pedro Resende

Symfony with React.js frontend Integration

The Internet brave new World

Post by Pedro Resende on the 29 of October of 2016 at 20:22

For the last three months, I've been working for a customer doing a migration from Wordpress to a custom solutions with React.js + Azure Microservices.

I have to say that at the beginning, React.js seemed to be a bit strange, however, today, I believe it's a perfect candidate to integrate with Symfony.

I'll explain a possible integration, this is only and example, if any of you believe there is a better approach, please leave a comment.

Let's start by installing Symfony

$ symfony new reactintegration

Now that we have a symfony installation, let's begin with the part of react.js integration

$ cd reactintegration
$ npm init .

Now that we have a npm project at the root of our Symfony installation, lets install the needed packages

$ npm install --save react react-dom babel-loader babel-core babel-preset-es2015 babel-preset-react react-hot-loader react-router webpack jquery
Let's start by creating a babel presets for react and ES2015. This will allow to write ES2015 and React.js JSX into current javascript
$ vi .babelrc

Add to the file

{
    "presets" : ["es2015", "react"]
}

Edit package.js file and add the following on the "scripts" section

  "scripts": {
    "build": "webpack",
    "watch": "webpack --watch",
  },

Now, lets create our webpack.config.js

$ vi webpack.config.js

Add the following inside

var webpack = require('webpack');
var path = require('path');

var BUILD_DIR = path.resolve(__dirname, 'web');
var APP_DIR = path.resolve(__dirname, 'react');
var config = {
    entry: [
        APP_DIR + '/app.jsx',
    ],
    output: {
        path: BUILD_DIR,
        filename: 'app.bundle.js',
    },
    module: {
        loaders: [{
            test: require.resolve("jquery"),
            loader: "imports?jQuery=jquery"
        }, {
            test: /.jsx?$/,
            loader: 'babel-loader',
            exclude: /node_modules/,
            query: {
              presets: ['es2015', 'react']
            }
        }, {
            test: /\.css$/,
            loaders: ['style', 'css', 'less']
        }]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new webpack.DefinePlugin({
            'process.env': {
                'NODE_ENV': JSON.stringify('production')
            }
        }),
    ],
};

module.exports = config;

Now, lets create our React.js source

$ mkdir react

$ vi app.jsx

I'll add some demonstration code, that will allow you to see this example in action

import React from 'react';

import {render} from 'react-dom';

import {
    Router,
    Route,
    IndexRoute,
    Link,
    browserHistory,
    Redirect,
    IndexRedirect
} from 'react-router';

var Homepage = React.createClass({
    render: function() {
        return (
            HEllo World
        )
    }
});


var Homepage2 = React.createClass({
    render: function() {
        return (
            HEllo World2
        )
    }
});

var Homepage404 = React.createClass({
    render: function() {
        return (
            404
        )
    }
});

render((
        <Router history={browserHistory}>
            <Route path="/">
                <IndexRoute component={Homepage}/>
                <Route path="hi" component={Homepage2}/>
                <Route path="*" component={Homepage404}/>
            </Route>
        </Router>
), document.getElementById('app'));
We're almost there, now we all need to do is add the app.bundle.js to our Symfony part
$ vi app/Resources/default/index.html.twig

Replace all the content with

{% extends 'base.html.twig' %}

{% block body %}
    <div id="app"></div>
{% endblock %}

{% block javascripts %}
    <script src={{ asset('app.bundle.js') }} type="text/javascript"></script>
{% endblock %}
Let's prepare our Controller to deal with React.js
$ vi src/AppBundle/Controller/DefaultController.php

Replace the all the content with

<?php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class DefaultController extends Controller
{
    /**
     * @Route("/api/{slug}", name="API")
     */
    public function apiAction(Request $request, $slug = null)
    {
        // replace this example code with whatever you need
        // Symfony Backend integration
    }

    /**
     * @Route("/", name="homepage")
     * @Route("/{slug}", name="homepage2")
     */
    public function indexAction(Request $request, $slug = null)
    {
        // replace this example code with whatever you need
        return $this->render('default/index.html.twig', [
            'base_dir' => realpath($this->getParameter('kernel.root_dir').'/..').DIRECTORY_SEPARATOR,
        ]);
    }
}

Now, eveytime you access to "/" our "/{slug}" it will used react-router, the "/api/{slug}" it will use Symfony

To end, we need to "compile" react.js into our app.bundle.js, to do that run the following command

$ npm run watch

This way, everytime you make a change to app.jsx it will re-create a new app.bundle.js file

Now start symfony's server by running

$ php bin/console server:run

and access with your browser to 

http://localhost:8080/

You'll see "HEllo World", if you try to access "/lsakdjjksda" you'll be reddirect to the 404 React.js route but if you access to "/api/" the response will be from symfony.

That is it, please let me know if you propose a different approach, the code is available at this link.