I’ve been working on a solution that once combined with @timbrandin’s https://github.com/timbrandin/sideburns or a forked version of it should eliminate the Blaze vs React debate. Below I’ll also introduce a solution for the Flux Pattern based on @SkinnyGeek1010’s amazing use of the “Alt” flux implementation ( http://alt.js.org ).
The general idea is a shared component-based interface between Blaze 2.0 and our own wrapper of React that looks almost identical to React minus a few things. That means component-based Blaze 2 interface (similar to @mitar’s Blaze Components) that is both a natural progression for Blaze and similar enough in interface to React to be agnostic of the underlying renderer. So you build your app with blaze, and at a later point in time you switch out the rendering engine from Blaze to React.
Here’s a quick example of the core things this shared interface must do to interface with both Blaze and React without requiring a transpiler of the non-template js:
class MyComponent extends Meteor.Component {
//Instead of accessing props at `this.props`, we use it as a function so Tracker automatically makes it reactive.
//And since the property won't actually exist at `this.props._id`.
//Under the hood the Blaze version of `this.props` is doing something like this: `return Template.currentData()._id`.
//In other words, the props are the data context, which is passed from parent to child template similarly in both React and Blaze.
//As for React, ots wrapped in such a way that `this.props(key)` simply calls a function that returns `this.props[key]`!
//And the same of course goes with `this.getState()`, which in the Blaze version would call:
//`Template.instance().activeCategoryId.get()`.
activeCategory: function() {
return this.props('_id') === this.getState('activeCategoryId') && 'active';
},
//`this.setState()` in the Blaze version of course calls `Template.instance().activeCategoryId.set()`
//My system creates one ReactiveDict in fact for each Component that requires it.
'click ul.categories li': function() {
this.setState('activeCategoryId', this.props('_id')); //note: setState can also take an object as in React proper
}
}
There would be some caveats, the main one being that if you don’t follow all the unidirectional requirements of React, it would break with React as the rendering engine. That said there would be prescribed patterns that if you follow would prevent this. The major pro here is that we preserve the ease of development that Blaze offers to newer developers (and for smaller apps, or at the beginning of development of large apps). As @mitar has put it, the main concept here is that React is akin to a “strongly typed” language whereas Blaze would be a “weakly typed” language. That means React notifies you right away when you’re doing things wrong whereas Blaze lets you get away with it. Things like optional linters could be implemented to curb issues here as well, as well as checks in our abstraction itself. But inevitably not all could be caught. And that is actually fine–it lets you “prototype” your app (like Meteor always has excelled at), and when you want to switch to React (say because you want to start moving parts of your app to React Native, or take advantage of other things in the React eco system), you deal with all the “typing” errors or whatever you wanna call them then.
In addition, in this shared abstraction we simplify and automate a lot of things that are verbose with React. My personal opinion is that React and Flux are great architecture patterns, but horrible interfaces. This is clearly shown by all the Flux implementations (minus the fact that Facebook originally implemented Flux generically to display the architecture design). And this is to be expected, it’s a natural progression–the core ideas have been presented, and now it’s time to do it better. I wouldn’t expect the first implementation to be the best (think of Google vs Yahoo and the other original search engines).
So, we can do this for the React architecture in a way that actually uses React as the renderer but does so in a Meteor/Blaze-consistent/appealing way. If we do this right, the real idea here is that we only have to transpile Spacebars (taking into account helpers and events defined on components and automatically attaching them as props), and the javascript interface in the components is the same for both. The system will know which renderer you’re using and switch out the code called for things like this.props()
and this.get/setState()
;
The most important things to consider though isn’t transpiling this shared interface, but where the data is coming from. This is especially important with Meteor since this is where React leaves off and Meteor definitively takes over. That “takeover” must be on point. There is a lot to be learned from both the Flux pattern and Relay. The reason being that, when either is used correctly, the end result is a unidirectional chain of “dumb/pure” components, which is the crux of making an app that can render both with Blaze and React. Every component being a “smart” component is exactly where all our Meteor/Blaze apps go wrong. The second data is queried into a component that the “react component” wasn’t reactively aware of via getMeteorData
everything breaks down. Relay and Flux style “state” management offers a lot more capability than one autorun
running in getMeteorData
. They however are both unnecessarily verbose (I mean you’re now requiring lots more code and complexity of your developer), and are redundant with a lot of what Tracker and Minimongo already do.
So the first step is deciphering the difference between “data”-based state from Minimongo, and what I’m calling “template only” or “template specific” state. We need solutions for both, the latter of which is one of the core problems Meteor developers are having when they move from global Session
storage to “scoped” storage on instances.
Here’s an example of the interface I’m working on that addresses Relay style template-level minimongo “data state” that trickles down to child templates/components:
class MyComponent extends Meteor.Component {
subscriptions() { //ran in `getMeteorData` (Mongo-specific Relay style template level subscriptions)
return [
//this is my automamagic subscription system (/w relations + aggregates):
{model: 'Post', name: 'recent', limit: 10}, //Post.recent() called below will return only the contents of this subscription
{model: 'Post', name: 'one', with: 'comments', params: '_id'}, //:_id param in URL
{model: 'Category', name: 'all'},
function() {
//if you use a function to subscribe to data, you can reactively depend on
//data from other subscriptions as the data becomes available:
if(Post.one()) Author.subscribe('one', {_id: Post.one()._id}); //though the relations system above would also handle this [contrived example]
}
];
},
//minimongo "data state" which can be accessed via `this.data()`:
categories: function() {
//note: in Meteor.Model I have features to handle subscription scoping via generated methods
//based on the subscriptions listed above. the subsription 'all' in fact is a complete description of
//what's passed to `find()`, which exists on its Model on the server. You have options to parameterize it
//from the client to an extent, but the server has complete control what's rendered.
return this.data('Category').all(); //accessing "sideways loaded data" guaranteed to only be
},
posts: function() {
return this.data('Post').recent();
//So in both these helper methods, we are accessing "sideways loaded data" guaranteed to only be
//the documents you want, and guaranted to be only ones that re-render the template because we use
//generated methods that precisely represent the document sin your subscriptions.
//`all` and `recent` are the generated methods based on your subscription configurations.
},
//It's important we distinguish with the above "sidewise loaded" "data state" from mini mongo
//which is very much automatically handled for you vs "template only" style state below
//which requires handling unidirectionally ourselves in order for the React renderer to behave:
//"template only" style state:
onCreated: function() { //with the React renderer would call `componentDidMount` -- both could be used interchangeably
let categoryId = this.data('Category').all().fetch()[0]._id;
this.setState('activeCategoryId', categoryId);
},
//alternatively, getInitialState will do the same as `onCreated`
getInitialState: function() {
return {
activeCategoryId: this.data('Category').all().fetch()[0]._id,
}
},
//helper that uses "template only" state:
activeCategory: function() {
return this.props('_id') === this.getState('activeCategoryId') && 'active';
},
//event handler that uses the same "template only" state:
'click ul.categories li': function() {
this.setState('activeCategoryId', this.props('_id'));
}
}
Now let’s look at the actual templates to see how this all comes together:
<template name="PostComponent">
<div>
<h1>{{post.title}}</h1>
<p>{{post.content}}</p>
</div>
<template>
<template name="MyComponent">
<div class="blog-list">
{{#each post in posts}}
{{> PostComponent post=post category=activeCategory}}
/**
Will transpile to `<PostComment post={this._get('post')} category={this._get('activeCategory')} />`
`this._get()` will first search `this.props` and then `this.state`.
Actually that's not how this would transpile since we'd have to first transpile #each into a pure js loop,
but it shows as an example how `props` and `state` are handled internally by `this._get`. The idea being
that React has you distiguish between State and Props, but in spacebars you don't. There obviously would be
a performance reduction as a result, but it's likely small. Smaller than how Blaze looks up tokens by far.
You could be explicit for performance (at the expense of verbosity and looking less like Blaze):
`{{> PostComponent post=props.post category=state.activeCategory}}`.
The main point is that your code looks very much like the good 'ol Meteor/Blaze you're used to
except you're forced to explicitly pass the context in a way symetrical with how `props` are passed in React.
This is similar to how `this.props()` works in helpers/events/etc: in React it simply returns this.props.thing
but in Blaze, because of Tracker, you need a function to be called. So we use a function in both interfaces,
but in React simply call the standard way via one extra short frame in the call stack.
**/
{{/each}}
</div>
//Lets look more deeply at how we would iterate through an array/cursor
//and also let's see how in fact we don't need to explicitly name passed props through "spread attributes":
<ul class="categories">
{{#each categories}}
{{> CategoryComponent}}
{{/each}}
</ul>
/** would transpile into:
let cats = this._get('categories').map(function (cat) { //_get finds categories on props, state OR METHODS
return (
<CategoryComponent {...cat} /> //spread attributes!
);
});
return (
<ul>
{cats}
</ul>
);
**/
</template>
Now let’s look at how helpers + event handlers are automatically passed down:
<template name="CategoryComponent">
<li class="{{activeCategory}}">{{name}}</li> //there is a click handler attached to .category!
<template>
The transpiler automatically passes down any click handlers to elements
with matching selectors in child templates (and their children’s children and so on). And automatically
passes any helpers as props through intermediary templates until it reaches any child templates that implement them.
The state for both the helper and event handler of course is maintained by the controller component at the top:
<li className={activeCategory} onClick={this['click .category']}>{{name}}</li>
//note: probably can only work statically at compile time.
So what’s most important is how this child component’s “template-only” state is managed by a Parent component who houses the helpers and event handlers. The event handlers and helpers are automatically made available to child components. I see that as our biggest challenge in making React compatible with the shared interface we’re going for. It’s also the crux of how “template only” state should be managed in Blaze in general regardless of React. In plain Blaze event handlers are already handled this way; just the context of the top level parent instance is lost. My components system as well as @mitar’s Blaze Components solves that. And now we also pass down Helpers, and of course maintain the context there too. So that means in Blaze we are saved all the work of passing down all these props and handlers. In react, the transpiler does the same thing for us. In essence, we bring a lot of the automagic Blaze offers surrounding contexts to React. And in Blaze we finally have a system/pattern to put plain “template-only” state in the right place at the top, in a way that’s easily and naturally reachable by child templates.
This doesn’t solve leaf components that need to share state. And any significant size app will soon have that problem.
It’s what lots of people have been discussing in this thread and in others–besides clobbering global Session storage
how can we manage state in a scoped unidirectional way that multiple leaf components can accesss???
Here’s my solution–or rather proposed solution. Whereas the stuff above is actually all built and being used by me,
these are ideas I just started working on thanks to @SkinnyGeek1010’s https://github.com/AdamBrodzinski/meteor-flux-leaderboard
and Alt which his work there introduced me to: http://alt.js.org . Alt is one of the cleanest Flux
implementations I’ve seen, and really nails some things. I still thinks the interface is far from what I’d like it to be,
but that’s where the following comes into the picture:
class MyActions extends Meteor.Action {
//configuration method to define basic actions.
//They only dispatch action (of some name) with any arguments:
simpleActions() {
return ['somethingHappened', 'triggerEvent', 'tweets'];
},
//do more than just pass arg to listening stores:
foo(param) {
//do some work with param
this.dispatch(param);
}
//showcasing async:
findTweets(searchTerms) {
this.dispatch(); //dispatches 'findTweets' since no action name is passed
Meteor.call('bar', searchTerms, (error, result) => {
this.dispatch('tweets', result.tweets); //showcasing calling another action
});
}
//trigger multiple actions from one action:
triggerSeveral(param) {
this.dispatch('somethingHappened');
this.dispatch('triggerEvent');
this.dispatch('foo', param);
}
}
class AnotherActions extends Meteor.Action {
//just another Actions to serve as an example of the many-to-many relationship between Actions and Stores
}
class TwitterStore extends Meteor.Store {
actions() {
return {
MyActions: ['findTweets', 'tweets'],
AnotherActions //es6 shorthand for: `AnotherActions: AnotherActions`, which causes store to bind all its actions
};
}
onFindTweets() {
//setup empty tweets array some view components see its empty and show a loading spinner
this.setState('tweets', []);
}
onTweets(tweets) {
//perhaps do some manipulation of the tweets
this.setState('tweets', tweets);
}
}
class AnotherStore extends Meteor.Store {
//just another store to serve as an example of the many-to-many relationship between Components and Stores
}
class TweetComponent extends Meteor.Component {
subscriptions() {
return [
{model: 'Post', name: 'recent', limit: 10},
TwitterStore, //unlike actions <-> stores that may bind together only some actions, the idea behind subscribing
AnotherStore //to a store is you get all of its state & its up to the component to use from state
];
},
'blur input': function() {
//calls this.ref.search in React, and $('[ref=search]') in Blaze; NO NEED TO TRANSPILE JS LIKE TEMPLATES!
//We wrap React with facade layer similar to how we do with Blaze Components
let searchTerms = this.ref('search').val();
MyActions.findTweets(searchTerms);
},
//also note that subscribed to stores automatically apply their state to initial state
tweets() {
let tweets = this.getState('MyStore').tweets(); //similar to how `data` is scoped as a param to this.data('Model')
return _.isEmpty(tweets) ? null : tweets; //in Blaze will only re-render if `tweets` prop of MyStore has changed
}
}
<template name="TweetComponent">
<input type="text" ref="search" />
<div class="tweets">
{{#if tweets}}
{{#each tweets}}
{{> TweetComponent}}
{{/each}}
{{else}}
{{> Spinner}}
{{/if}}
</div>
</template>
So first, a few things must be discussed so we are on the same page with the goals here. As usual, everything
coming out of the Facebook camp is unnecessarily complicated. Flux and its benefits can be explained a lot more easily. The core concepts of Flux are 2 sets of many-to-many
relationships:
- Events <-> Stores
- Stores <-> Components.
An event-based system is prescribed to make those relationships. All in all, its purpose is to decouple these 3 aspects. It’s not
much different than a many-to-many or through relationship, say, in ActiveRecord, or any event system that allows you to move endlessly amounting logic out of the core event emitting object into however many client objects want to listen to it. It should be configurable
via the least amount of syntax possible. Redux attempts that through a functional reducer-based system,
but it doesn’t fit with the configurable & consistent class-based system I prescribe.
A few classes have been presented here–there commonalities should be apparent. The subscriptions
property
initially displayed is pure configuration, and that’s just the tip of the ice berg for how my system works.
In short, the Models class (not discussed here) is chalk-full of configuration, same as many other utility classes.
So with the goal of reducing our code as closely to pure configuration as possible, what you’re looking at
is simply specifying those relationships between actions and stores and then with stores and components, much
like Alt. Alt is the inspiration. And it was actually the perfect inspiration for the style of my overall system.
Now back to the above example. It’s kind of a contrived example because you only have one leaf component
in need of state. Its purpose was just to first show how it would work. Lets move to adding a second leaf component
that needs to share the state:
<template name="PageComponent">
{{> HeadlineComponent}} //leaf component A
{{> TweetComponent}} //leaf component B
</template>
<template name="HeadlineComponent">
<h1>CURRENTLY SEARCHING: {{searchTerms}}</h1>
</template>
class HeadlineComponent extends Meteor.Component {
subscriptions() {
return [
TwitterStore
];
},
searchTerms() {
return this.getState('MyStore').searchTerms();
}
}
class MyActions extends Meteor.Action {
//...
findTweets(searchTerms) {
this.dispatch('findTweets', searchTerms); //now `searchTerms` are passed to any stores that want to put in state
Meteor.call('bar', searchTerms, (error, result) => {
this.dispatch('tweets', result.tweets);
});
}
}
class TwitterStore extends Meteor.Store {
//...
onFindTweets(searchTerms) {
this.setState('searchTerms', searchTerms); //and now `searchTerms` is state shared by both leaf components
this.setState('tweets', []);
}
//...
}
They can now talk to each other unidirectionally without having to query state from each other. The TweetComponent
can display the tweets and let you search for tweets, and the HeadlineComponent
can display the search terms. Neither maintains that state.
The job of asyncronously searching for the tweets (and keep in mind that these are fresh tweets directly from twitter) is in an Action. I actually copied almost exactly what is shown in the Alt tutorial in the async section: http://alt.js.org/guide/async and rewrote it in this style.
If you read that page, you will see that the decision is yours about where you put the async work. He doesn’t higlight what factors might help you make that decision, but to me it seems obvious: if you have multiple stores that depend on the results of the async call, put the call in an action, but if the action is specific to one store, put it in the store. I originally had my example doing just that–it retrieved facebookPosts as well, and dispatched those to the Facebook store. But then I realized I was making the example too complicated. Anyway, you get the idea–the async call should go in TwitterStore
.
All in all this highlights the flexibility of this decoupling–Actions can talk to multiple Stores. And conversely, multiple Actions can speak to the same Store. Similarly, Components can bind themselves to multiple Stores, and a Store can be bound to multiple components.
In the system laid out above, stores are treated just like Subscriptions–you’re subscribing to a store of “template-only” style data, rather than mini mongo data. Its data however is put in regular state rather than the .data
property like data from getMeteorData
, i.e. the subscriptions called within it. In both cases, you don’t have too manually fetch the data or set the state–that’s automatically handled for you by specifiying the “subscription”. And in both cases you have a symmetric API to get the scoped data when the subscriptions resolve: this.data('ModelName').subscriptionName()
and this.getState('StoreName').stateKeyName()
. There are a few other options there for how that could look, but since in the rest of my system you are doing things like Post.all()
which calls Posts.find(/* subscription config selector + options */), decided that this was the most consistent. Another option would be this.data('Post', 'all')
. But as you can see that is less reminiscent of the way that involves the subscription-named methods I tack on the Model class. However, it does have one benefit: it looks more like something will prevent you from fetching data not scoped to you’re subscription–it feels almost as though that’s the only way to get the data. The idea being we’re trying to steer the user into the “pit of success” of not fetching data subscribed to from the current subscription, which would break React. Anyway, from where I am now, I favor preserving the Model.subName() interface (or something closer to it) over babying developers. That in fact is my main gripe with React. I rather have the option to make all the mess we Blaze developers have made these past few years, but with new prescribed patterns to avoid it, not to mention these new tools which guide you in that direction.
Anyway, in conclusion, via the subscriptions
config array you have a Mongo-specific Relay style interface, and via Meteor.Actions
and Meteor.Store
you have a Flux interface. As for the Relay style interface, Meteor automatically prevents double-subscriptions to the same data-sets, so it’s like Relay in that way–you can have multiple leaf components subscribing to the same data without worrying about double-subscribing. You can then use that data to trickle down the tree of components below each leaf. If however you have “template-only” state (which is needed by more than the children in one tree, i.e. leaf components), you can use the Flux pattern. And if however, you have “template-only” state which is needed only by the children of one “Controller Component”, then you can use the unidirectional top-bound event/helper instance state technique I first showed. That in fact is what I’ve been using for a while now, and quite successfully. I’ve been falling back to Session storage for leaf component communication, and have been wondering how to solve it, and above is the solution I’m about to build. Any input would be much appreciated because I’ll be opening my framework up to all you guys soon.
Lastly, I wonder if any minimongo state would ever make sense in the Flux style Actions and Store components–is relay style “sideways data loading” via subscriptions in controller components enough? I’ll leave that for another day, but I imagine it too could easily find a home in the aforementioned Flux style classes. I’m excited to see how much value they add, rather than clutter it only seems to produce in the plain React world. Not to dog React too much, but Minimongo goes a long way for us. A big part of my research here has been deciphering the difference (the strengths and weaknesses) of Minimongo, and other kinds of state. All the state discussed in the React world isn’t assuming you have all the benefits of Minimongo and Tracker at your disposal. You essentially have a lot less “state” to deal with because of it. So rather than jumping to creating all your Flux stores and actions before you make your first component, I see a future where only once you add that second leaf node do you begin to abstract your state code out into an Action and Store. That’s a major win for the Meteor community because one of the biggest selling points of Meteor is a novice can easily dive in. We don’t want to lose that. Someone just coming from HTML/CSS can bootup Meteor, bundle up some static web design code, and quickly start moving to becoming a dynamic developer. I see React (and especially Flux) being 25% or less as effective at helping that novice web designer level up their skills into becoming a full on web developer. This is also important to us more skilled developers because we work with web designers, because we do smaller projects, because we like to prototype quickly, because there’s so much to think about already–any productivity gains are a big deal for us. I hate the word “automagic”, but add some of it to these newly emerged paradigms/patterns, and we can get all that stuff with a lot less code, a lot less files. We can achieve the dream of both having a toolset that is easy to reason about as your app gets larger and swift and natural to get up and running with minus the boilerplate.
Super final thought: after discussing all the Flux stuff we can get get, I forgot to tie it back to the shared Blaze/React interface. The idea there is that the Flux stuff is actually super easy to build just for Meteor and my class-based system, and we will have that. But how does Flux transpile (or whatever)? Basically, it’s the same as my prescription for the React/Blaze merger: use functions instead of props. That way blaze can reactively re-render. The Flux implementation too will have a more pure event-based solution under the hood that doesn’t rely on tracker that propagates props/state from actions to stores to components. Hell, it could maybe even use Alt under the hood. I haven’t thought that far yet. First things first, if we can get React to render for Blaze 2.0 (even if a few constraints are required), we’re going to have a bright future–one where we don’t have to watch in disappointment as non-Meteor developers use a bunch of effective tools we can’t. At certain points early on NPM was something that we struggled with. Arunoda’s package to use NPM outside of packages only came out like a year and a half ago. Async on the server is still an issue on the server for those that haven’t groked how to use futures. Either way we’ve gotten over this stage and most of have no problems including NPM packages. Just look at how many are wrapped in Atmosphere. So a path like this will get us the same for the React world. I hope to release my Components system soon so it will shed even more light on how easy it will be to transpile to React. @timbrandin’s Sideburns is a step in the right direction, but it’s for Blaze 1.0. That’s a mismatch. That doesn’t lead to an environment where you can just switch out the renderer and have your Blaze Components look almost identical to what React is actually rendering. And that will be crucial for debugging, taking advantage of other React tools, and switching back and forth between Blaze and React. It will likely be crucial for actually meeting all the goals of being able to 100% transpile to React in the first place–Sideburns still has a ways to go. In conclusion, Blaze Components 2.0–whatever you wanna call it–is more important than ever, no matter which path we end up taking–but hopefully we end up taking the middle path.