Meteor, React and Material UI the easy way

[UPDATE 2015-06-27] Meteor MDG is changing its skin and now they provide a Meteor + React + Material UI integration example, which is a bit different to the one suggested here.

There are technologies that are cool standalone but don’t immediatly play nice together.


material-ui-demo.meteor.com (source)

  • Meteor: the fullstack web platform with data synchronization client/server (DDP)
  • React: the virtual DOM UI library from Facebook
  • Material UI (React): the implementation of the Material UI / Polymer components without the webcomponents part (aka the Material UI that works today)
  • Babel: the the ES6/7 transpiler and the easiest way to deal with React components.

Meteor (MDG) is working actively to support officially React and Babel, but the React ecosystem uses npm/browserify as package manager, so let’s see how to integrate all together in a maintainable way(*)

Meteor + React + Material UI base

Currently the Meteor-React packages (react-packages) aren’t in the public index and the react package doesn’t run nice with Material UI package (see #11), so we'll install React via browserify and the rest a la Meteor way.

git clone https://github.com/meteor/react-packages /path/to/react-packages
export PACKAGE_DIRS=/path/to/react-packages
meteor add babel-compiler babel-runtime jsx react-meteor-data

Note: /path/to is where you keep your local/private packages

Then we create a local package browserify-deps, to store all our browserify dependencies. It could also be useful to install react-router or others React deps.

meteor add cosmos:browserify
meteor create --package browserify-deps
// packages/browserify-deps/client.browserify.js
React = require('react');
mui = require('material-ui’);
injectTapEventPlugin = require("react-tap-event-plugin");
// packages/browserify-deps/packages.js
Package.describe({
  name: 'browserify-deps',
  version: '0.0.1',
});

Npm.depends({
  'react': '0.13.3',
  'material-ui':'0.8.0'
});

Package.onUse(function(api) {
  api.use(['cosmos:browserify@0.3.0'], 'client');
  api.addFiles(['client.browserify.js'], 'client');

  api.export('React', 'client');
  api.export('mui', 'client');
  api.export('injectTapEventPlugin', 'client');
});

And now let's install our package

meteor add browserify-deps

Our Meteor + React + Material UI is ready and we can now start coding!

The React reactive counter with DatePicker example

Just to wire all the things together here is a minimal example

// client/Main.jsx
var {RaisedButton, DatePicker, AppBar, AppCanvas} = mui;
var ThemeManager = new mui.Styles.ThemeManager();

Main = React.createClass({
  // Required by Material UI
  childContextTypes: {
      muiTheme: React.PropTypes.object
  },
  getChildContext() {
      return {
          muiTheme: ThemeManager.getCurrentTheme()
      };
  },

  // Use `Session` or other reactive data sources ONLY
  // if you have to communicate with the rest of the Meteor stack
  componentWillMount() {
    Session.set('counter', 1 );
  },

  // Required to use Meteor reactive data sources
  mixins: [MeteorDataMixin],
  trackMeteorData(props, state) {
    // Put here your subscriptions
    return {
      counter: Session.get('counter')
    };
  },

  // react component private functions
  _reset() {
    Session.set('counter', 0 );
  },
  
  _increment() {
    Session.set('counter', this.data.counter + 1 );
  },

  _onChange(err, newDate) {
      console.log(newDate);
  },

  // We can customize the internal format date. Default is mm/dd/yyyy
  _dateFormat(date) {
    var m = date.getMonth() + 1;
    var d = date.getDate();
    var y = date.getFullYear();
    return `${d}-${m}-${y}`;
  },

  render: function() {
    var styles = {
      paddingTop: '200px', textAlign:'center'
    }

    return (
      <AppCanvas>
        <AppBar title='Meteor ❤ React ❤ Material UI' iconClassNameRight="muidocs-icon-navigation-expand-more"/>
        <div className="spacer" style={styles}>
          <RaisedButton label={this.data.counter+" +1" } primary={true} onTouchTap={this._increment} />
          <RaisedButton label="reset" primary={false} onTouchTap={this._reset} />
          <DatePicker hintText="I'm a DatePicker try me!!" mode="portrait" formatDate={this._dateFormat}
                      onChange={this._onChange} />
        </div>
      </AppCanvas>
    );
  }

});

We don't use any router in this example so, let's attach the react component when Meteor is ready.

// client/startup.jsx
Meteor.startup(function () {
  // Required by Material UI http://material-ui.com/#/get-started
  injectTapEventPlugin();
  // React component mounted in the DOM
  React.render(<Main />, document.body);
});

If you want to learn more about React, Meteor, Material UI, I raccomand the todo example with React, Meteor and react-router (source, the demo todo with Material UI (demo, source) and the TodoMVC with grigio:babel and grove:react (demo, source).

As you can see there are many ways to integrate Meteor + React, and the cosmos:browserify way (source) is very interesting because do not embeds source code in your project but link directly the mainstream releases.

What do you think? Discuss on crater.io

(*) With this approch the React library included is not the one minified by FB.