Philipp Kyeck
Philipp Kyeck

Reputation: 18860

structure of a Backbone and React single page app

I've been using backbone.js for most of my frontend JavaScript projects so far, but after hearing about Facebook's react.js I got interested and started poking around.

I'm wondering whether or not I'm supposed to use the Backbone.View class anymore or replace every "view" with a react "component" - even the "base-view" that's layouting the page ...

I found some posts in the interwebs that still used Backbone.Views - others I stumbled upon only created React-classes.

Maybe someone can point me in the right direction ... what to use when and how to best implement multiple "pages/states" in single page app and not just the usual (I can stand it anymore) TODO example.

Here some code that I came up with:

Backbone init stuff:

require(
  [
    "jsx!app/view/base",
    "react",
    "app/router",
    "backbone"
  ],
  function (BaseView, React, Router, Backbone) {
    "use strict";

    var router = new Router();
    var base = new BaseView({router: router});

    React.renderComponent(base, document.getElementById("page"));

    router.on("route", function(action) {
      base.setProps({path: action});
    });

    Backbone.history.start({pushState: true});
  }
);

app/router:

define(function(require) {

  "use strict";

  var Backbone = require("backbone");

  /**
   *
   */
  var Router = Backbone.Router.extend({
    routes: {
      "": "home",
      "test": "test",
      "*error": "404"
    }
  }); // end Router


  return Router;
});

app/view/base.jsx:

define(function(require) {

  "use strict";

  var React = require("react");

  var mixins = require("app/utils/mixins");

  var Header = require("jsx!app/view/header");
  var ContentHome = require("jsx!app/view/content_home");
  var ContentTest = require("jsx!app/view/content_test");
  var ContentLogin = require("jsx!app/view/content_login");

  /**
   *
   */
  var BaseView = React.createClass({

    render: function() {
      var content;

      switch (this.props.path) {
        case "home":
          content = <ContentHome />
          break;
        case "test":
          content = <ContentTest />
          break;
        case "login":
          content = <ContentLogin />
          break;

        case "404":
        default:
          content = "Error, page not found";
          break;
      } 

      return (
        <div id="base" onClick={this.onClick}>
          <Header />
          {content}
        </div>
      );
    },

    onClick: function(event) {
      if (event.target.tagName.toLowerCase() === "a" &&
          event.target.className === "main") {
        event.preventDefault();
        this.props.router.navigate(event.target.pathname, {trigger: true});
      }
    }

  }); // end BaseView

  return BaseView;
});

But I'm not completely pleased with this setup. Any input is welcome.

Upvotes: 15

Views: 4544

Answers (2)

Khaled Jouda
Khaled Jouda

Reputation: 283

  var BaseView = React.createClass({
    pathToComponent: function(){
        var paths = {
             'home': ContentHome,
             'login': ContentLogin
        };
        return paths[path] || Content404
    },
    render: function() {
      var ContentComponent = this.pathToComponent(this.props.path);
      return (
        <div id="base" onClick={this.onClick}>
          <Header />
          <ContentComponent />
        </div>
      );
    },

  }); // end BaseView

  return BaseView;
});

Upvotes: 1

Yazad D
Yazad D

Reputation: 441

Don't hold a reference to a component like you are. If needed use refs or better yet, just render the component in the callback. React will check if it needs to update the DOM.

require(
  [
    "jsx!app/view/base",
    "react",
    "backbone"
  ],
  function (BaseView, React, Backbone) {
    "use strict";

    var router = Backbone.Router.extend({
      routes: {
        "": "home",
        "test": "test",
        "login": "login",
        "*error": "404"
      }
    }).on('route', function(action) {
      React.renderComponent(
        <BaseView router={router} path={action} />,
        document.getElementById("page")
      );
    });

    Backbone.history.start({pushState: true});
  }
);

Edit: Since the logic in BaseView is very coupeld to the logic in Router, it makes most sense to keep them in the same file.

var BaseView = React.createClass({
  render: function() {
    return (
      <div id="base" onClick={this.onClick}>
        <Header />
        {this.props.children}
      </div>
    );
  },

  onClick: function(event) {
    if (event.target.tagName.toLowerCase() !== "a") return;
    if (event.target.className !== "main") return;

    event.preventDefault();
    this.props.router.navigate(event.target.pathname, {trigger: true});
  }
});

app/router:

var router = Backbone.Router.extend({
  routes: {
    "": "home",
    "test": "test",
    "login": "login",
    "*error": "404"
  }
}).on('route', function(action) {
  var pathMapping = {
    "home": ContentHome,
    "test": ContentTest,
    "login": ContentLogin
  };
  var Content = pathMapping[action] || Content404;

  React.renderComponent(
    <BaseView router={router}>
      <Content />
    </BaseView>,
    document.getElementById("page")
  );
});

Backbone.history.start({pushState: true});

Upvotes: 2

Related Questions