Adding React to an Existing App

In notebook:
FrontEndMasters React
Created at:
2016-06-21
Updated:
2016-06-21
Tags:
Webpack libraries React JavaScript jQuery
The great thing about React is that you can just start using it immediately. You don’t have to learn new technologies. 
The hardest part could be the build and put in a new library. 

Backbone todomvc

Will migrate a part of Backbone todomvc.
localhost:8080/7-migrating-to-react/backbone-todomvc/

So far it’s 100% Backbone.

First, let’s add React to the app

Adds a JSX transformer (transforms JSX in the browser) (index.html)
<script src=“node_modules/react/dist/JSXTransformer.js"></script>
and react
<script src=“node_modules/react/dist/react.js"></script>

The view

Convert the footer element to React:
  this.$footer.html(this.statsTemplate({
  completed: completed,
  remaining: remaining
}));
Need to grab the #stats-template (in index.html, just a standard ​<script type=“text/html” id=“stats-template”>​ with htm and templating in it) and creates stats.JSX
Need to add this too. (We’re not using Webpack)
<script type=“text/jsx” src=“js/views/Stats.jsx”></script>
  // **** Stats.jsx ****
var app = app || {};

app.Stats = React.createClass({
  render: function () {
    // 3. ++++ add var remaining (remaining number of todos)
    var remaining = this.props.remaining;
    // 7. ++++ add completed variable
    var completed = this.props.completed;
    return (
      // 1. ++++React always needs a root element...
      <div>
        // 2. ++++ paste here
        // paste the #stats-template html into here...
        // 4. ---- then instead of
        // ----<strong><%= remaining %></strong>
        // 4. ++++ do this syntax
        <strong>{remaining}</strong>
        
        // 5. ---- and change this
        // ---- <%= remaining === 1 ? 'item' : 'items' %> left
        // 5. ++++ do this
        {remaining === 1 ? 'item' : 'items'} left
        
        // 6. ---- refactor the if statement
        // ---- <% if (completed) { %>
		// 	---<button id="clear-completed">Clear completed (<%= completed %>)</button>
		// 	---- <% } %>
        // 6. ++++ turn if statement into an expression
        {completed > 0 && (
          <button id="clear-completed">Clear completed ({completed})</button>
        )} 
      </div>
    );
  }
})
The view is converted, need to update the Backbone app to use it
  // ****  js/views/app-view.js ****
/*global Backbone, jQuery, _, ENTER_KEY */
var app = app || {};

(function ($) {
	'use strict';

	app.AppView = Backbone.View.extend({

		el: '#todoapp',

		statsTemplate: _.template($('#stats-template').html()),

		events: {
			'keypress #new-todo': 'createOnEnter',
			'click #clear-completed': 'clearCompleted',
			'click #toggle-all': 'toggleAllComplete'
		},

		initialize: function () {
			this.allCheckbox = this.$('#toggle-all')[0];
			this.$input = this.$('#new-todo');
			this.$footer = this.$('#footer');
			this.$main = this.$('#main');
			this.$list = $('#todo-list');

			this.listenTo(app.todos, 'add', this.addOne);
			this.listenTo(app.todos, 'reset', this.addAll);
			this.listenTo(app.todos, 'change:completed', this.filterOne);
			this.listenTo(app.todos, 'filter', this.filterAll);
			this.listenTo(app.todos, 'all', this.render);

			app.todos.fetch({reset: true});
		},

		render: function () {
			var completed = app.todos.completed().length;
			var remaining = app.todos.remaining().length;

			if (app.todos.length) {
				this.$main.show();
				this.$footer.show();
				
				// 1. ++++ Render with React
				React.render(React.createElement(app.Stats, {
					completed: completed,
					remaining: remaining
				}), this.$footer[0]);

				//--- this.$footer.html(this.statsTemplate({
				//--- 	completed: completed,
				//--- 	remaining: remaining
				//--- }));

				this.$('#filters li a')
					.removeClass('selected')
					.filter('[href="#/' + (app.TodoFilter || '') + '"]')
					.addClass('selected');
			} else {
				this.$main.hide();
				this.$footer.hide();
			}

			this.allCheckbox.checked = !remaining;
		},

		addOne: function (todo) {
			var view = new app.TodoView({ model: todo });
			this.$list.append(view.render().el);
		},

		addAll: function () {
			this.$list.html('');
			app.todos.each(this.addOne, this);
		},

		filterOne: function (todo) {
			todo.trigger('visible');
		},

		filterAll: function () {
			app.todos.each(this.filterOne, this);
		},

		newAttributes: function () {
			return {
				title: this.$input.val().trim(),
				order: app.todos.nextOrder(),
				completed: false
			};
		},

		createOnEnter: function (e) {
			if (e.which === ENTER_KEY && this.$input.val().trim()) {
				app.todos.create(this.newAttributes());
				this.$input.val('');
			}
		},

		clearCompleted: function () {
			_.invoke(app.todos.completed(), 'destroy');
			return false;
		},

		toggleAllComplete: function () {
			var completed = this.allCheckbox.checked;

			app.todos.each(function (todo) {
				todo.save({
					completed: completed
				});
			});
		}
	});
})(jQuery);
That’s it. The app still works. We’re not blowing away the DOM at this element.

As you bring React into your app and convert more and more views, your app starts to become more and more performant. 

Two-way binding can sometimes be difficult (he’s converting a 20 000 lines app at his work). React has a different paradigm for data-flow, so you will probably need to touch that too.