Backbone + React

Backbone + React

Some time ago a teammate and I implemented some POC with Backbone and React and from the beginning I thought it would be interesting to combine these two frameworks, as Backbone only provides a lightweight base for scaffolding the app and React seems a very powerful library to build the UI. But the first implementation I tried made use of JSX, from React Tools, which has been deprecated in favor of Babel (Facebook’s React Blog).

This post documents my second attempt of integrating Backbone with React in a web application that uses RequireJS to define modules and Grunt to preprocess the JSX files and wrap everything up.

Backbone + React example I

You can download the full source code of this example at Github.

Backbone + React example I

 

This is the folder structure. The “src” folder contains all the source files, that will be proccessed by a Grunt build script, putting the resulting file structure inside “dist” folder.

 

 

 

 

 

Ok, let’s see the code…

  <body>
        <script data-main="js/main" src="../node_modules/requirejs/require.js"></script>
  </body>    
  • index.html: Will only load require.js and refers to main.js as our application’s entry point. All the HTML content of the different views of the app will be located in React components. We will compose these views with Backbone.
'use strict';
require.config({
    // shim: scripts that do not call define() to register a module
    shim: {
        underscore: {
            exports: '_'
        },
        backbone: {
            deps: [
                'underscore',
                'jquery'
            ],
            exports: 'Backbone'
        },
        backboneLocalstorage: {
            deps: ['backbone'],
            exports: 'Store'
        }
    },
    paths: {
        'jquery': '../../node_modules/jquery/dist/jquery',
        'underscore': '../../node_modules/underscore/underscore',
        'backbone': '../../node_modules/backbone/backbone',
        'text': '../../node_modules/requirejs-text/text',
        'react': '../../libs/react',
        'ReactDOM': '../../libs/react-dom'
    }
});

define([
    'backbone',
    'views/MainView'
], function(Backbone, MainView) {
    'use strict';
    return Backbone.Router.extend({
        init: function() {
            window.app = {};
            window.app.vent = _.extend(Backbone.Events);    
            new MainView();
        }            
    });
});
  • main.js: This file contains the RequireJS configuration. The shim field is used to configure dependencies for scripts that do not call define() to register a module. The paths field contains shortcuts to the different libraries that we will use. Backbone has underscore and jQuery as dependencies, so we are loading all of them, and React on its side is split into React and ReactDom.
define([
    'underscore',
    'backbone',
    'views/HeadView',
    'text!templates/main.html'
], function(_, Backbone, HeadView, mainTemplate) {
    'use strict';

    return Backbone.View.extend({
        el: 'body',
        template: _.template(mainTemplate),
        events: {},
        initialize: function() {
            this.render();
        },
        render: function() {
            this.$el.html(this.template({}));
            this.postRender();
        },
        postRender: function(){
            this.headRegion = this.$('#head-region');
            if (!this.headView) {
                this.headView = new HeadView();
            }
            var props  = {title: 'Header Title'};
            this.headView.render(this.headRegion, props);    
        }
    });
});
  • MainView.js: Is a Backbone View that will use a standard way of rendering HTML templates with underscore & text. With this configuration, we can use either React or underscore to render the HTML corresponding to a Backbone View. After MainView renders its contents, it will render the React component inside the header DOM element (#head-region), passing the title property to the component.

 

define([
    'backbone',
    'ReactDOM',
    'react',
    'components/HeadComponent'
], function(Backbone, ReactDOM, React, HeadComponent) {
    'use strict';

    return Backbone.View.extend({
        events: {},
        initialize: function() {},
        render: function(el, props) {
            //Create React Element based on HeadComponent.jsx
            this.reactElement = React.createElement(HeadComponent, {
                view: this,
                title: props.title
            });
            //Render element into DOM element passed from parent view
            ReactDOM.render(this.reactElement, el[0]);
        }

    });
});
  • HeadView.js: Here is when we start using React. The HeadView is a Backbone View that receives a DOM element as parameter in its render method. We will call React.createElement() passing to the call the JSX component and the title we received from MainView. Once the element has been created, we call ReactDOM.render() to actually insert the rendered contents into the DOM.

 

define([
    'react',
], function(React) {
    'use strict';
    return React.createClass({
    render : function() {
        return (
            <div>
            <span id="header-title">{this.props.title}</span>
            </div>
        );
    }
  });
});
  • HeaderComponent.jsx: It is a simple header that will take a string named “title” as parameter and render a span with the received string. The props field of a React element is populated with the properties passed to the React.createElement() call that is made to create that element.
module.exports = function(grunt) {

  require('load-grunt-tasks')(grunt);
  grunt.loadNpmTasks('grunt-execute');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-express');  

  grunt.initConfig({

    clean: ["dist"],

    babel: {
      options: {
        sourceMap: false,
        modules: "common"
      },
      dist: {
        files: [{
          expand: true,
          cwd: 'src',
          src: ['js/components/*.jsx'],
          dest: 'dist',
          ext: '.js'
        }]
      }
    },

    copy: {
      main: {
        files: [{
          expand: true,
          src: ['**/**', '!**/components/**'],
          dest: 'dist/',
          cwd: 'src',
          filter: 'isFile'
        }],
      },
      libs: {
        files: [{
          expand: true,
          src: ['libs/**'],
          dest: 'dist/'
        }],
      },
      node_modules: {
        files: [{
          expand: true,
          src: [
            'node_modules/requirejs/require.js',
            'node_modules/jquery/dist/jquery.js',
            'node_modules/underscore/underscore.js',
            'node_modules/requirejs-text/text.js',
            'node_modules/react/**',
            'node_modules/react-dom/dist/react-dom.js',
            'node_modules/backbone/backbone.js'
          ],
          dest: 'dist/'
        }],
      }
    },
    express: {
      all: {
        options: {
          port: 3000,
          hostname: 'localhost',
          bases: ['./dist'],
          livereload: true
        }
      }
    },
    watch: {
      scripts: {
        files: ['src/**/*.js', '!src/js/components/**'],
        tasks: ['copy'],
        options: {
          spawn: false,
          livereload: true
        },
      },
    }

  });
  grunt.registerTask('server',['express','watch']);
  grunt.registerTask('default', ['clean', 'copy', 'babel', 'server']);

};
  • gruntfile.js: A simple Grunt script to copy all the source files to “dist” folder, process the JSX files with Babel and open a light node server.

 

Next steps I plan to take are to add some user interaction, handling the events with React and implement the Backend calls with Backbone Models. And Webpack maybe.

Still there? You are awesome <3

 

2 Replies to “Backbone + React”

  1. Awesome article, clear and nice explanation with amazing code examples. Even better is the plan to continue on going deeply ​ with Backbone Model and React events and using Webpack.

    1. Thank you very much, Bojan! An honor to have you here. Let’s see if you like the second part, too. I think we are on the good track :))
      Cheers!

Leave a Reply to Bojan Golubovic Cancel reply

Your email address will not be published. Required fields are marked *