Adding ReactJS components to an existing jQuery front-end

For a ZF2 project I’ve been using a traditional approach where the server-side renders the HTML. For interactive views and AJAX stuff I’ve been using jQuery, but as we all know this can get messy as soon as the views are becoming more complex.

I had been playing with ReactJS already, but until now I’ve never used it in a live application. I’m planning to slowly replace some components of the views with React components.

My current project structure looks like this (typical for ZF2):

The main challenge I’m expecting to walk into is getting Babel and Webpack setup so my ES6 code will be converted to ES5 and my JSX is automatically converted into native JavaScript (Babel) and creating one .js file with my React code and its dependencies (Webpack).

Since I don’t want to throw my existing front-end code structure overboard I need to have them play nice side-by-side.

My first idea is to just create a separate folder to hold my React sources. I’ve created this folder in the root of my project folder and named it reactjs. I don’t want to put it in the public folder, since the sources shouldn’t be accessible.

 

First I initialized npm, to be able to use it for package management:

cd reactjs
npm init

Once that’s done you’ll have a package.json file in your new reactjs folder.

The next step is to install and setup Webpack:

npm i webpack -S

After installing webpack we’ll have to create a config file for it:

touch webpack.config.js
var webpack = require('webpack');
var path = require('path');

var BUILD_DIR = path.resolve(__dirname, '../public/js');
var APP_DIR = path.resolve(__dirname, 'src/app');

var config = {
    entry: {
        family_view_create: APP_DIR + '/family_view_create.jsx'
    },
    output: {
        path: BUILD_DIR,
        filename: "[name].bundle.js"
    }
};

module.exports = config;

I’ve set it up so I can add multiple bundles in the future. Every bundle will be packaged into the defined output folder using the format: [name].bundle.js

In my case the output bundles will end up in my public/js folder.

To test if this works you can create a test file in reactjs/src/app. In my example: reactjs/src/app/family_view_create.jsx

In the jsx file add some debug code, i.e.: console.log(‘test’);

Then test to see if webpack works and creates the output in public/js/family_view_create.bundle.js

node_modules/webpack/bin/webpack.js -d

The next step is to setup Babel to transpile ES6 code and JSX to ES5.

Install babel with the required plugins:

npm i babel-loader babel-preset-es2015 babel-preset-react -S

Then we need to create the .babelrc file with its settings:

touch .babelrc

The content of my .babelrc file looks like:

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

Next we have to tell Webpack to use Babel. In webpack.config.js add the loader:

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

var BUILD_DIR = path.resolve(__dirname, '../public/js');
var APP_DIR = path.resolve(__dirname, 'src/app');

var config = {
    entry: {
        family_view_create: APP_DIR + '/family_view_create.jsx'
    },
    output: {
        path: BUILD_DIR,
        filename: "[name].bundle.js"
    },
    module : {
        loaders : [
            {
                test : /\.jsx?/,
                include : APP_DIR,
                loader : 'babel'
            }
        ]
    }
};

module.exports = config;

Next install the react and react-dom modules:

npm i react react-dom -S

Now you should be ready to write some React code and get it to transpile and bundle to the public/js folder.

I’ve created a quick example in reactjs/src/app/family_view_create.jsx:

import React from 'react';
import ReactDOM from 'react-dom';
import ShareableDriversList from './components/ShareableDriversList.jsx';

class FamilyViewCreate extends React.Component {
    render() {
        return <div><ShareableDriversList /></div>;
    }
}

ReactDOM.render(<FamilyViewCreate />, document.getElementById('test-container'));

The FamilyViewCreate is the main component that will be rendered in the div with id ‘test-container’ and it will be built from a hierarchy of sub-components. I like to keep my code structured so I’ll work with a directory structure like this:

The ShareableDriversList.jsx is in reactjs/src/app/components and will looks something like this:

import React from 'react';

export default class ShareableDriversList extends React.Component {
    render() {
        return (
            <div>Shareable drivers block</div>
        );
    }
}

In your view (html) you will have to include the generated bundle js file, in my case public/js/family_view_create.bundle.js:

 <script type="text/javascript" src="/js/family_view_create.bundle.js"></script>

You also have to create the container div (or any element) to host the main React component. In my case:

<div id="test-container"></div>

If everything went well you should be able to have Webpack create the bundle again:

 node_modules/webpack/bin/webpack.js -d

Then when you load the page the React component should be rendered (just plain text at this point).

You may have noticed how huge the bundle file is. In my case it came down to a whopping 728kB. In production you’d want to minify the bundle and again Webpack can take care of this automatically. Add this to your webpack.config.js file:

 plugins: [
        new webpack.optimize.UglifyJsPlugin({minimize: true})
    ]

This should drastically reduce the size of the output file.

Of course while developing you don’t want to manually run Webpack continiously. The easiest way to handle this is to run this command to have Webpack watch the source files for changes:

./node_modules/.bin/webpack -d --watch