Smoothly feature up your Blaze Apps with React Components


#1

Hi, I was working on some updates/upgrades to some of our apps and would like to do it with React. Also I looked for the minimal effort to just replace the “hurting” parts but do not replace the whole app.

Setup of the existing app was:

  1. Meteor 1.1.0.2
  2. FlowRouter
  3. FlowLayout
  4. Materialize-SASS

First of all I migrated to Meteor 1.2.0.2, updated the packages and add the React ones

New Setup for app without any changes on sources

  1. Meteor 1.2.0.2
  2. FlowRouter
  3. BlazeLayout
  4. React
  5. React-Template-Helper
  6. ReactLayout
  7. Materialize-SASS

So far - so easy. I will proceed my explanation with just a small welcome app in mind with 3 pages

  1. / = Welcome
  2. /about = About Page
  3. /imprint = Imprint Page

Currently all pages are done with Blaze, so my app looks like:

  1. head.html
  2. router.js
  3. layout.html
  4. sidebar.html
  5. welcome.html
  6. about.html
  7. imprint.html

The routes at routes.js are looking like:

FlowRouter.route('/', {
  action: function(params) {
    BlazeLayout.render('appLayout', {page: 'pageWelcome', app_data: { title: 'Hello World'} });
  }
});

The sample layout.html is defined as:

<template name="appLayout">
  {{> appSidebar}}
  <div class="page-main">
    {{> Template.dynamic template=page}}
  </div>
</template>

and my welcome.html is just simple:

<template name="pageWelcome">
  <div class="page-welcome">
    <h1>{{app_data.title}}</h1>
    <p>Hello world!<p>
  </div>
</template>

First Goal:

Replace the about.html|.js with React as about.jsx:

PageAbout = React.createClass({
  render() {
    return (
      <div className="page-about">
        <h1>{this.props.app_data.title}</h1>
        <p>React about page!</p>
      </div>
    );
  }
});

Minimal change for layout.html

Well that’s easy - now how to call? I do not want to replace my complete layout, so I just changed this a bit and use the function from package react-template-helper to insert React component into Blaze views:

<template name="appLayout">
  {{> appSidebar}}
  <div class="page-main">
    {{#if hasReactComponent}}
      {{> React component=reactComponent app_data=app_data}}
    {{else}}
      {{> Template.dynamic template=page}}
    {{/if}}
  </div>
</template>

So I defined just one clause to switched between Blaze and React rendering. All I need to do finally, was to change the action on FlowRoute:

FlowRouter.route('/about/', {
  reactComponent: function() { return PageAbout; },
  action: function(params) {
    BlazeLayout.render('appLayout', {app_data: { title: 'Hello World'} });
  }
});

To get this altogether we need two small template helpers at layout.js:

Template.appLayout.helpers({
  hasReactComponent: function() {
    return !_.isUndefined(FlowRouter.current().route.options.reactComponent);
  },
  reactComponent: function() {
    return FlowRouter.current().route.options.reactComponent();
  }
});

That’s it - I just exchanged a very small part of my app.


Next goal

In case that all from previos was smoothy, I wished to replace also some more functional part of my app with some new React components. Therefor I cannot use my main layout.html anymore but need to run ReactLayout.

So I changed the extension to router.jsx and created that new route first:

FlowRouter.route('/blog/', {
  action: function(params) {
    ReactLayout.render(AppLayoutReact, {content: <PageBlog />, app_data: { title: 'Blog Posts' } });
  }
});

and follow up with the new React component layout_react.jsx:

AppLayoutReact = React.createClass({
  render() {
    return (
      <div>
        <div className="page-main">
          {this.props.content}
        </div>
      </div>
    );
  }
});

Well so long … easy again BUT … what about my sidebar.html

Inside the new React layout I have to rebuild my sidebar content but won’t double my sources.

For that I created a new package and add that to my app:

  1. 4commerce:blaze-as-react

That allows to use Blaze templates as React components. My layout_react.jsx:

AppLayoutReact = React.createClass({
  render() {
    return (
      <div>
        <React.BlazeView template="appSidebar" />
        <div className="page-main">
          {this.props.content}
        </div>
      </div>
    );
  }
});

Wow! Amazing! - I am impressed how easy to mix the elements


Big thanks to MDG for that nifty meteor and @arunoda and his team for those great packages

Cheers
Tom


How properly migrate from Blaze to React?
FlowRouter 4.0 and Future of Routing in Meteor
Where I think Meteor is doing wrong with Blaze
[Article] What's Going On With Blaze, React, and Meteor?
What is the alternative to bootstrap ? Which one do you use?
Mongo query in FlowRouter route
#2

I wonder, what are the advantages of such approach except for being able to use React components in place of my Blaze components? That is, which of React good points (such as easy server side rendering) will my app share by using that approach except for being able to use jsx?


#3

Really awesome! I’m glad this refactor path is working out for you. Excited to read more.


#4

If you are looking on the React features about props and states and how to exchange the values between any level of components, you will like that if you need that.

Whenever you already thought about get Template.parent.parent.parent…data, then this is a suitable path for you too.

At least we use now most the time React in our apps and e.g. we prefer that kind of source code instead having to declare helpers for any retrivials.

Just my 2cents
Tom


#5

Today I needed to improve some parts I missed yesterday.


#6

This part:

<div class="page-main">
     {{#if hasReactComponent}}
        {{> React component=reactComponent app_data=app_data}}
     {{else}}
        {{> Template.dynamic template=page}}
     {{/if}}
</div>

Should be

{{#if hasReactComponent}}
  <div class="page-main">
    {{> React component=reactComponent app_data=app_data}}
  </div>
{{else}}
 <div class="page-main">
    {{> Template.dynamic template=page}}
 </div>
{{/if}}

Otherwise you get an error:

{{> React}} must be used in a container element. Learn more at

I’d do this as a ‘suggested edit’ but can’t see a way to do that.

Thanks so much for your well written post - was just what I was looking for.


#7

Are there any downsides to mixing React/Blaze code?

Does it make the download of the app much bigger?

Would be interested to make use of Apollo client and since it isn’t available for Blaze at the moment, it would make sense to use the React client, but I’d have to mix that into a Blaze app.