In this episode we’re taking on the whole component hierarchy, from smart and dumb components, all the way to layouts and routes.

Everything in React is a component, but that doesn’t mean all these components necessarily play the same role.

In the previous episode we ported a simple widget from Blaze to React and then included it from a Blaze template, but this time we’ll work on the whole chain, from what you see on screen all the way up to the route that triggers it.

Smart & Dumb Components

Whenever you’re working with components, a good practice is splitting them into smart and dumb components.

Smart components (sometimes also called container components or controller components) handle subscriptions and data management, and pass on the output to dumb components, which take in these props and render the appropriate HTML code.

We previously wrote about this pattern in Blaze, but React makes it even easier to apply it.

In this case, the dumb component is called Video:

Video = React.createClass({

  propTypes: {
    video: React.PropTypes.object.isRequired
  },

  getDescription() {
    return {
      __html: parseMarkdown(this.props.video.description)
    }; 
  },

  renderResource(resource, index) {
    return (
      <li key={index}><a href={resource.url}>{resource.title}</a></li>
    )
  },

  render() {
    return (
      <div className="video-page">
        <div className="back-link">
          <a href="/"> Back to home</a>
        </div>
        <div className="player">
          <iframe src={"https://player.vimeo.com/video/"+this.props.video.code+"?title=0&amp;byline=0&amp;portrait=0&amp;color=f3cc14"} width="500" height="281" frameBorder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>
        </div>
        <div className="infos">
          <h2>{this.props.video.title}</h2>
          <div className="description" dangerouslySetInnerHTML={this.getDescription()}>
          </div>
        </div>
        <div className="links">
          <h3>Links:</h3>
          <ul>
            {this.props.video.resources.map(this.renderResource)}
          </ul>
        </div>
      </div> 
    )
  }
});

And the smart one is VideoPage:

VideoPage = React.createClass({

  mixins: [ReactMeteorData],

  getMeteorData() {
    return {
      video: Videos.findOne({slug: FlowRouter.getParam("slug")}),
      user: Meteor.user(),
      product: Session.get("book")
    }
  },

  componentDidMount() {
    $(".player").fitVids();
  },

  render() {
    if (!this.data.user) {
      return <Layout><div><a href={FlowRouter.path("loginPage")}>Please log in</a></div></Layout>
    } else {
      if (this.data.user.hasPermission(this.data.product, "full")){
        return <Layout><Video key={this.data.video._id} video={this.data.video} /></Layout>;
      } else {
        return <Layout><div>Please upgrade!</div></Layout>
      }
    }
  }

});

Layout Components

React also makes it very easy to use components as layouts to wrap other components.

This is what the Layout component looks like:

Layout = React.createClass({

  render() {
    return (
      <div className="layout">
        <Header />
        <div className='main' id='main'>
          <div className='row'>
            {this.props.children}
          </div>
        </div>
        <Footer />
      </div>
    );
  }

});

The key part here being this.props.children: it’ll get replace by whatever content is included in between the two <Layout>...</Layout> tags.

The App Component

Finally, just like with Blaze the top-most component acts as a general controller for our whole app, subscribing to global subscriptions and showing a loading screen until they’re ready:

App = React.createClass({

  mixins: [ReactMeteorData],

  getMeteorData() {

    var data = {
      ready: false
    };

    var handles = [
      Meteor.subscribe('site', SITE_KEY),
      Meteor.subscribe('toc'),
      Meteor.subscribe('chapters', BOOK_KEY),
      Meteor.subscribe('interviews', BOOK_KEY),
      Meteor.subscribe('videos', BOOK_KEY),
      Meteor.subscribe('thisUser'),
      Meteor.subscribe('pages'),
    ];

    if(_.every(handles, handle => {return handle.ready();})) {
      Session.set("book", Products.findOne({key: BOOK_KEY}));
      data.ready = true;
    }

    return data;
  },

  showLoading() {
    return (
      <Layout><div className="loading-loader">Loading</div></Layout>
    )
  },

  componentDidMount() {
    loadFonts();
  },

  render() {
    return this.data.ready ? this.props.content : this.showLoading();
  }

});

This is the component that actually gets called as a layout by React Layout in the route (which is where this.props.content comes from):

FlowRouter.route('/video/:slug', {
  name: 'video',
  action: function () {
    ReactLayout.render(App, {
      content: <VideoPage />
    });
  }
});

Finally, note that because of a known issue with React Layout and Blaze Layout, you can’t actually combine both in the same app at this time.

One way around this is to keep the routing and app controller layout in Blaze, and include React templates via the technique we covered previously.