@luisherranz it seems you really like to re-invent the wheel every step you take is basically what the space framework boils down to at the end of the day. You would even get a more convenient class system in Javascript for free with dead-simply dependency injection that is not tied to Meteor.autorun
(which makes it even easier to test).
The approach is still different. Iām only using a simple flux dispatcher. The rest is pure Meteor, but organised āthe flux wayā. No classes for stores, mediators, commands, constantsā¦
Actually, what Iāve been thinking the last week is that I need to accept the way Meteor works, and if Meteor is based on the use of globals, I canāt go against that, because then you can run into problems and overcomplicate things.
My reactive dependency injection package does four things:
- Solves circular dependencies.
- Gets rid of loading order problems.
- Gets rid of globals.
- Add dependency injection.
But Meteor is designed to be used with globals and to solve loading problems using packages and Meteor.startup().
In most cases, globals, loading order and circular dependencies can be solved by using namespaces, like:
MyApp = {};
MyApp.SomeStore = {......};
With my Dependency package you can use dependency injection in your test initialisations like this:
beforeEach(function(){
// Internal Dependencies
var countersStore = new CountersStore();
var catalogRouter = new CatalogRouter();
Dependency.add('CatalogRouter', catalogRouter);
Dependency.add('CountersStore', countersStore );
// Store we want to test
var catalogStore = new CatalogStore();
});
But if you use globals instead, you can do:
beforeEach(function(){
// Internal Dependencies
CountersStore = new CountersStore();
CatalogRouter = new CatalogRouter();
// Store we want to test
CatalogStore = new CatalogStore();
});
which is simpler and it just works.
So I donāt think my Dependency package is worth anymore and I will try to get rid of it in my code to keep things as simple as possible (and as meteorish as possible).
Iāve been thinking about the āEverything is a packageā pattern as well.
I watched the video of the Telescope refactoring. Itās very interesting:
Sacha namespaced everthing under the a Telescope
global and everything is now in packages.
I have used the āEverything is a packageā pattern in the past, for an application where third party people were able to add functionalities.
The problem I found with this pattern is that packages have to talk with each other, and strong dependencies appear. In consequence, packages are weaker and less reusable. And a new question arises: āHow should packages communicate with each other?ā.
Flux is a great way to communicate different parts of a modern app, so I see great potential combining āEverything is a packageā and Flux. I suggest:
- Namespace your app, like Telescope does.
- Put as much logic in packages as you can.
- Make packages as agnostic as you can: avoid dependencies between packages.
- Use stores mostly to interconnect those agnostic packages between each other and the UI.
- Avoid using stores for complex logic: abstract it and create reusable packages.
There are a lot of packages out there which depend on Iron Router for example. Thatās not good. What if I want to use Flow Router, or not to use a router at all?
The only exception is where your package is extending the functionality of other package, like accounts-ui extending accounts-base.
If you want your app to be extensible by third party people you still need to add stuff like hooks (filters) and modules like Telescope does (I yet have to figure out how can a flux dispatcher and hooks for extensibility work together).
Everything above is only my opinion. Feel free to comment or criticise : )
Hey @luisherranz,
I am already using the āeverything is a packageā pattern and it works great. Also the Space.Injector
solves all of your mentioned problems without being Meteor-aware (which is also something a lot of people forget, its just Javascript, not Meteor)! And I am not really sure why runtime dependencies should be reactive, this makes the code even more complicated and coupled.
Here is your example how I would write and test it with space:base
:
// Simple sugar for javascript inheritance (not required)
CatalogStore = Space.Object.extend({
// just a prototype property, no magic
Dependencies: {
countersStore: 'CountersStore',
catalogRouter: 'CatalogRouter'
}
});
beforeEach(function(){
// Store we want to test
var catalogStore = new CatalogStore({
countersStore: new CountersStore(),
catalogRouter: new CatalogRouter()
});
});
in your real app you want the injector to wire things up:
var injector = new Space.Injector();
var countersStore = new CountersStore();
var catalogRouter = new CatalogRouter();
injector.map('CountersStore').to(countersStore);
injector.map('CatalogRouter').to(catalogRouter);
var catalogStore = new CatalogStore();
injector.injectInto(catalogStore); // provide runtime dependencies
this was the bare-metals approach, only using the injector and doing everything else by hand.
Of course space comes with convenience like namespace lookups:
var injector = new Space.Injector();
injector.map('CountersStore').asSingleton();
injector.map('CatalogRouter').asSingleton();
injector.map('CatalogStore').asSingleton(); // deps are auto-injected
One thing you donāt consider is this: there is no way to decouple everything. If any parts of your app talk with each other (in any way) then they are coupled (in some way). Maybe not to implementation details (the Meteor way) but even with your dispatcher and/or dependency injection the various parts are coupled at runtime -> its the messaging contract that couples them.
Think about it: you can build the most generic, decoupled package possible, but still something, somewhere has to send to or retrieve messages from it. These messages are coupling, even when they are abstracted behind string constants and extremely loosely contracts like with flux dispatcher. In my opinion thatās even worse, because now you have coupled things together but have no explicit contract defined how messages have to look like.
Coupling means this: āDo I have to change anything else if I change this part of the code?ā. So if you change how your package receives parameters (or which) then you have to update all callers too, even if they never have a direct reference to the receiver.
The other aspects like namespacing, globals etc. are not really relevant and a matter of taste. There are people that like the ājava namespacing patternā and others who hate it. You can build your structure any way you want and even the Meteor way (simple globals) has its place.
With dependency injection you can remove the static coupling (which is great for testing) but you can never remove runtime coupling, because thatās what your app is made of. (Actually you can, there is a pattern called āanti-corruption layerā which means that you use mediators between parts of your app that translate the api messages of both parties, but thatās another story).
In my experience it works like this: You start mostly with very app-specific code, small encapsulated classes that do one thing well and some ācontrollerā-like parts that wire them up. Eventually there appear some bigger-scope concerns where you think āhmm ā¦ this could become its own packageā ā but still it doesnāt have to be āgenericā or āagnosticā. The code inside the package can be extremely self-coupled and straight-forward. Then eventually you come across functionality that is really re-usable, like e.g. a package that wraps the Stripe api which you could use in more than one application.
The āgenericā package, shared between multiple apps sounds great BUT it comes with a big price tag: maintenance of the API contract. In my opinion you have to carefully consider where it pays off and which parts of the code should not become too generic.
To sum it up: the only benefit you get from using a messaging dispatcher / event bus / any kind of middleware layer, is that you can hook into the message flow. Thatās it, there is nothing more. Forget about these patterns in other languages (where they are used to decouple), we are in Javascript land where its even possible to switch your pants while you are shitting them. Think: I could replace Meteor
with something else, before calling a method that uses it ā this would be a (hacky) way of dependency injection.
But what I really like about messaging patterns is the possibility to make the contract between packages explicit. Because thatās one downside of dynamic languages: you canāt rely on the compiler to tell you if you called something āthe right wayā which is a pain while refactoring.
Here is a simple self-checking message that you can send to my stripe package:
class Stripe.ChargeCustomer extends Space.Struct
@fields:
customerId: String
total: Number
description: String
ipAddress: String
this uses Meteor check
internally when you instantiate the struct with a param object. Of course I use these messages also in my unit-tests etc. so that if you change the api, everything breaks and tells you that where you have to update your code to complete the refactoring.
Ok this is getting really long and confusing I hope its still understandable.
Itās perfectly understandable, and very interesting
I agree with most of things you say, actually.
It was reactive to solve circular dependencies. I agree with you that is complicated and you should avoid them in the first place.
Thatās very similar to what I was doing with the Dependency package, sure.
I donāt think itās needed anyway so I will try to get rid of it.
When I was talking about mixing āEverything is a packageā and Flux together, I wasnāt talking about including dependencies to the flux dispatcher in every package. You shouldnāt do that. Packages should have an API and thatās it. No dependencies, no communications.
The dispatcher is a concern of the Views and Stores only. Those are the things which are coupled and talk to each other so your packages can be decoupled and not know about the existence of other parts of the app.
If you want to put your stores/views inside the Meteor package system, itās ok. But I wasnāt considering them as packages/libraries.
Sure, some packages are going to be things nobody else needs. Thatās not bad, but I have run into problems having to refactor whole packages just because I was just coding fast and I wasnāt āforcing myselfā to make them as agnostic as I could. Then you change some part of your app and everything breaks.
I think if you force yourself to think that way, the quality of your code improves greatly. Even if your package is not useful elsewhere.
Totally agree.
Totally agree and I think that is very very useful.
For me Flux is that, a very simple dispatcher which forces you to create very simple, one-way, chains of events (dispatcher->store->app-state->view).
Iāve been taking a look at the Flux implementation which Optimizely is using in production: Nuclear-js
They have done some very interesting stuff:
- They use modules to encapsulate domains. They contain stores and expose actions and getters.
- They store all the app state in a single Immutable map.
- They do automatic data observation / rendering, very similar to Meteorās reactivity.
- Thanks to their reactivity, they donāt need the waitFor functionality.
This is great because it confirms that you donāt need waitFor in Meteor. I like the modules concept as well.
In the end, this made me think about different flux implementations. Iāve been thinking about space-ui as well. Our conversation looks like āwho is wrong and who is rightā and it shouldnāt. There are dozens of flux implementations out there and none of them is wrong or right. Space-ui is one of them and if you feel comfortable with it, go ahead. Iām sure @CodeAdventure put a lot of effort in it.
I really like the fact that Facebook only shared a bare dispatcher and let everyone else to roll out their implementations. Actually, Flux is an architecture and not a framework. The only important things are the Flux Principles.
With this thread I wanted to find out if Flux and Meteor can work together. Now I am sure they can, and I think it is actually an excellent idea.
Itās so simple to use Flux and Meteor and there are so many good implementations of Flux that I think is up to people deciding which implementation to choose. They can use my port of the Facebookās Dispatcher and do the rest of the stuff his way, or maybe use something like space-ui where you have to learn its API and follow it.
I think itās time for me to close this thread and write a Readme.md for the package.
I hope itās useful for people interested in using Flux with Meteor but only want the basics.
@luisherranz sorry if it came across as space:ui
vs. your flux implementation, thatās definitely not what I intended because I also replace space:ui
with other packages if they work in a better way (e.g blaze-components
is quite useful sometimes compared to my mediator pattern).
I wanted to get across that the āflux dispatcherā is just another event bus, a very common pattern which probably existed for 30+ years in software dev. The restriction that you can only dispatch one message per loop might be nice for Facebook but I never needed it, nor did it help me in any way. If you use Meteorās reactivity + single source of truth patterns than there is not much need for client-side messaging anyway because everything is data-driven.
One of the example by Facebook was the unread messages counter for the in-page messenger. They had some bugs with weird states etc. but honestly you will never have those problems with a ānormalā Meteor application because reactivity syncs all parts automatically for you. There is only one source of truth: the database.
The only really exciting thing about React / Flux is the possibility to do server-side rendering and the idea of āre-render everythingā which greatly simplifies your thinking about UI. Thatās what most people get excited about but only the second part can be applied to Meteor easily.
I actually move more and more away from using flux stores and messaging in the front-end because its just not needed (in most cases).
Anyway, thanks for putting all this research effort here, its definitely a great resource for people thinking about these architectures and also was a great mirror for myself (thatās why I couldnāt shut up :-D)
No it wansāt, no problem : )
In my opinion, Flux is more than just a message bus. Itās a set of useful, keep-it-simple, principles. Maybe not for all cases but, what is?
I agree, Meteorās reactivity is incredible and there is no need to stop using it.
Look here: https://github.com/facebook/flux/issues/209
No problem, your comments and expertise are welcomed of course.
I know you went thru all this but I wanted to do it as well.
By the way, in this interview, Matt DeBergalis said two interesting things:
- Old web is page-based but we can do better. Meteor is here to help us create apps, not webs.
IMHO, IronRouter centric apps are page-based again, thatās the reason I wanted to check something new. - So far, Meteor has been architecture agnostic but in the future that is going to change.
He actually said way more than two interesting things of course : )
Now I am about to start a big project where I have to design an extensible Meteor app, similar to what Sacha is doing with the Telescope refactoring. I will see how Flux can fit in a project like that (if at all) but I think I have an idea.
I hope I can get some time out soon to do a proper readme for the meteorflux:dispatcher package but I had a lot of work lately. Iāll do soon!
I have updated the README file of the package. I am sorry it took so long but Iāve been very busy.
https://atmospherejs.com/meteorflux/dispatcher
I hope it is clear enough to reflect what I have learned here, but if you feel like something is missing let me know and I will update it.
I am already studying other things, because the app I need to build now is more complex. I am testing Flux but with global state objects and global getters (like NuclearJS or Facebook and Baobab do) mixing it with an extensible pattern (like WordPress or Telescope) and a template system (like Ghost) all organised with modules using the everything-is-a-package pattern.
Anyway, this MeteorFlux Dispatcher will remain as simple as it is, so anybody wanting to build something with Meteor and Flux can try it out.
I may come back if I consider I have learned or worked with something which may benefit this thread. It looks like a lot of people was paying attention : )
And of course if you are playing with Meteor and Flux and have any doubt feel free to reply here and we will work it out together.
Cheers!
yes, your package supported methods that easy to change params and query params. And flow-router really suite for FLUX .
My stacks:
- meteorhacks:flow-router
- izzilab:iz-flux (just ported from facebook Dispatcher)
- reactjs:react
- izzilab:react-layout (my package to define layouts and render content for reactjs)
@luisherranz Thanks for this post, itās been great going through the thought process behind your implementation of Flux into Meteor. For the most parts I agree with you and I think the Flux architecture fits great into Meteor. I have a couple of thoughts regarding the dispatcher and the Stores.
The thought about the dispatcher is minimal, but since Dispatcher.dispatch()
always takes in an actionType
I think it would be more āmeteor likeā if it was called in the same way that Meteor.methods get called; eg. Dispatcher.dispach("SOMETHING_HAPPENED", "some data")
or Dispatcher.dispach("SOMETHING_HAPPENED", {firstName: āFirstā, lastName: āLastā })
. This would move slightly away from Facebookās implementation, but it makes the dispatcher look more like Meteor code.
Regarding the Stores, I donāt think they should be separated into Stores and App State. I think the stores should maintain the app state, both the variables (reactiveVar/Dict or Sessions) and the Collections. I see the stores publishing helpers directly to Blaze, essentially taking over most of what Template helpers normally do. The views/templates would directly query the Stores for the state via Store helpers. This would keep us from having helpers like:
Template.myTemplate.helpers({
myHelper: function() {
return MyStore.getMyValue()
}
});
Instead we would call {{ MyStore.getMyValue }}
directly from the template, making the code more clean and reusable.
For this I think it would be best to have a Store prototype that takes care of registering its helpers to Template and registering with the Dispatcher. Iāve created a package for doing that and the api is relatively simple:
PostStore = new Store('PostStore', function(){
// String ('PostStore') is the name of the Store,
// this is what the Blaze global helper will be called.
// Function that initiates variables and whatever
// else you might want to do. Basically this is .onCreated()
// (and it might as .onCreated() to act more like meteor)
var self = this;
self._visiblePosts = new reactiveVar(5);
});
PostStore.helpers({
getPosts: function() {
return Posts.find();
},
getVisibleNumber: function() {
return this._visiblePosts.get();
}
});
PostStore.actions({
userLoadsMorePosts: function() {
var self = this;
var number = self._visiblePosts.get
self._visiblePosts.set(number+5);
}
});
The Store package takes care of registering with the Dispatcher so the dispatcher gets called like normal, and it also registers the helpers with Blaze so the template can be
<template name="posts">
{{#each PostStore.getPosts}}
<h2>{{title}}</h2>
<p>{{text}}</p>
{{/each}}
</template>
Iāve created a fork of your excellent CartFlux example that uses this Store package, itās on GitHub and live here (should look identical).
Like I said, I think the Stores should handle the app state, and the helpers that refer to the app state should be contained within the Stores (or as Collection helpers, eg. calculating cart total price). I do see some role for Template helpers, but having it mostly limited to a visual representation of the app state (like which class to show, helpers for #if
blocks etc.)
I also think that the Stores should take care of their own subscription (since theyāre handling the app state), but I havenāt figured out how that should be done, any thoughts on that?
I havenāt posted the Stores package yet, since thereās a couple of things I want to figure out before I do (subscriptions, yes/no/how? better way for actions call other actions in same store ā¦). I will post it soon and Iāll update when I do.
I am new to Meteor but I know have heard of Flux Architecture and I am pretty sure I might need one. Have read your submissions and I get the concepts. I just wanna know if there is a link to your MeteorFlux like the way Facebook has for their Flux.
You might want to checkout Redux. Itās the latest and best of the flux frameworks. Facebook has even changed their implementation to include some of itās features.
Redux is also view agnostic so it will work with Blaze (or even no view layer!). Normally Redux uses regular arrays and objects for state but Blaze needs a reactive data source to update the views. Luckily this works rather well.
Hereās an example I made using Redux and the leaderboard app: GitHub - AdamBrodzinski/meteor-redux-example: Redux for Blaze
It includes a Blaze helper to access state. Note, with this setup weāre only using the state for local temp state stored in memory. You still can (and should) use actions to decouple to UI for database ops.
example:
// global reactive store is setup on app startup
store = createStoreWithMiddleware(appReducer);
// store has initial empty values:
// {
// selectedPlayerId: '',
// selectedPlayerName: '',
// foo: 1,
// bar: 2,
// }
// view calls action and returns into store dispatcher
//
Template.player.events({
'click': function () {
store.dispatch( Actions.selectPlayer(this._id) );
}
});
// action 'creator' is a normal function that
// just creates an action object and returns it
//
Actions.selectPlayer = function selectPlayer(playerId) {
let player = Players.findOne(playerId);
let playerName = player.name || "N/A";
return {
type: 'SELECT_PLAYER',
playerId: playerId,
playerName: playerName
};
};
// app reducer catches action in switch statement and can mutate the
// data based on action payload metadata. reactState is a reactive-dict
// so we just set the final value and return the dict when done.
//
// if the app was large we could have several reducers that combine the
// state into the final root object. Redux can only have 1 store
//
appReducer = function appReducer(state, action) {
state = state || reacState;
// see action_creators.jsx for action payloads
switch (action.type) {
case 'SELECT_PLAYER':
// we're setting the reactive dict here
state.set('selectedPlayerId', action.playerId);
state.set('selectedPlayerName', action.playerName);
// then returning the entire dict when done
return state;
case 'INCREMENT_SCORE':
// collections are in Minimono but you could also keep
// them here if its easier to have all data in one place
// see React repo for example of that
return state;
default:
return state;
}
}
// templates can consume this global state with the `store` helper
// but can only mutate it by calling an action
//
<template name="leaderboard">
{{#if store 'selectedPlayerName'}}
<div class="details">
<div class="name">{{store 'selectedPlayerName'}}</div>
</div>
{{else}}
</template>
@gunnarsturla I really like your solution. Itās been a great inspiration.
I am working right now in an AppState package to create and manage helpers and app state in a Flux application. It will work quite similar to your solution but it will be a singleton instead of embedded in each store. That way your views arenāt coupled to specific stores.
In the end, you will have a global AppState
and your stores will use it to expose data to the views with a very simple API. You can then retrieve the AppState from the views or other parts of Meter.
It will be quite smart, so you can add complex objects or functions returning cursors for example. And you can even mix them, and it will still work in a logical way.
Iām working on it right now. I will finish it next week I think.
You can install it using:
meteor add meteorflux:dispatcher
You can read all the info in github:
Iām checking Redux. Itās pretty cool actually.
I think we are all heading to the same conclusions here : )
These are the three principles of Redux:
-
Single source of truth:
I agree, absolutely. Thatās the reason Iām developing the AppState package, which is the same concept: an object tree. The main difference is that it uses Meteor reactivity and exposes itself to helpers by default. -
State is read-only:
Absolutely again. This means views should NOT be allowed to modify the app state. This is Flux. -
Mutations are written as pure functions:
Agree as well. This means you manage the mutations of the state in a special place called reducers. Not exactly but quite similar to managing them in stores. I donāt really care where those mutation functions are.
Hello,
thanks for the great and inspiring work.
what is the State of your āAppState packageā?? (pun intended )
I was/am just looking for something like this to solve the tangle of cross component communications in my app.
I really like the ideas and communication paradigms proposed by Redux / Flux / CQRS / EventSourcing, but I find that all of them are creating a lot of boilerplate code that donāt fit too much with meteorās reactivity.
Moreover I have some doubts about where to put the reducers in complex multicomponent scenarios. ā¦maybe you can help.
Iāve created a meteorpad example of a possible multi-component structure that exemplifies many possible interactions, aiming at creating a component structure where each component is truly independent and unaware of the others, besides of its children.
Basically I have:
- [VPL] - VideoPlayer List > a List of Subcomponents
-
[VP1] - VideoPlayer 1 > a component that contains others
- [VS1] - VideoSurface1 > a āleafā or basic component
- [PB1] - PlayButton1 > a āleafā or basic component
-
[VP2] - VideoPlayer 2
- [VS2] - VideoSurface2
- [PB2] - PlayButton2
- [PB3] - PlayButton > that is āhangingā just to indicate that it is outside of the list, but it could be anywhere else in the page
Imagine a Scenario where VP1 is playing and VP2 is stoppedā¦then the user clicks PB2 to Play the video.
the problem with the current MeteorFlux (or other Flux derived structures), as far as I can tell, is that if any PlayButton is clicked it will dispatch the event to the store {āPlayButton_Clickedā} and the following things might happen:
- All the reducers that are listening to that event will respond, which might be either in the same Playbutton, or in all VideoPlayers
- there is no way to tell which component is actually the one supposed to answer to that action,
- unless the action itself passes some kind of data about itself (ie: āhey this action comes from PB2ā)
- and unless the components who are listening are aware of who their children are and if they have to reply or not (if PB2 was pressed, only VP2 should answer, but the same reducer will be in VP1 so it will also answerā¦unless it knows that it shouldnāt because PB2 is not one of its children)
- Moreover, since we have an even higher level, the List (VPL) of components, there might be someone interested in that action too.
for example, when VPL receives a request for play, it might want to pause the VP1 that is currently playing before letting VP2 actually play. So there is another ācompetingā reducer that is listening on top.
the basic problem is: who is the āmediatorā / āobserverā, who has the authority to own the reducers (in an ideal scenario where we have not developed the components ourselves but importing them from some repository?)
If we put it into VPL we wonāt be able to use the VP by itself, which is not ideal.
to solve this I have thought of a ComponentTree package for which every component / template
- automatically gets assigned an unique ID
- knows which is its parent
- knows which are its direct and derived children
through this system, each component can automatically subscribe only to the events of its children, direct or derived.
this allows VP1 and VP2 to have completely isolated reducers that answer only the children events. which is good.
I still have some confusion about which way the mediators should act upon their own children (ie VPL telling VP1 to stop and VP1 to play). I can see 3 methods that have each their pros and cons:
1. (imperative) A > tells B.stop()
components expose a public API that parents use to control them imperatively
= components talk to each other directly
2. (declarative state) A > tells AppState.B.isPlaying = false
mediator components like VP or VPL modify the AppState and, since it is reactive, the subcomponents will react to its new state
= components talk to each other via the reactive AppState (which is like the Session object)
3. (declarative action) A > stores event {āVideoPlayer_Stoppedā, who: [VP1, VP2]}
mediator components emit another action event using an API that it knows children use and will react.
= components talk to each other only through the EventStore collection
what do you think?
how would you solve this with the current Flux / redux structure?
Should each app have a global EventStore and State Reactive dict (which is actually the Session object )?
Is state always derived from the EventStore (this is somewhat confusing to me)?
I donāt know if I understood correctly your problem, but you shouldnāt manage the action in any component. In Flux you should manage it in a Store and in Redux, in a reducer (which is like a store, but with more restrictions).
So, in your case, when the user clicks on the PB2 play button, an action like this should be dispatched:
Dispatcher.dispatch({
actionType: 'PLAY_BUTTON_PUSHED',
id: '2'
});
In Meteor, the id should be ideally the _id of the Minimongo object representing that video.
Then, in a Store (which is not one of the components) you should change the state of your app:
// declare some state variables...
var isVideoPlaying = new ReactiveVar(false);
// create store with getter methods...
VideoStore = {
getIsVideoPlaying: function(){
return isVideoPlaying.get();
}
};
// register to change state when action is dispatched.
VideoStore.tokenId = Dispatcher.register( function(payload) {
switch(payload.actionType) {
case 'PLAY_BUTTON_PUSHED':
isVideoPlaying.set(payload.id);
break;
});
And then, in the component which is controlling the play and stop functions:
Template.VP.onCreated(function(){
var self = this;
self.c = Tracker.autorun(function(){
var isVideoPlaying = VideoStore.getIsVideoPlaying();
if (isVideoPlaying === self._id) {
self.playVideo();
} else {
self.stopVideo();
}
});
});
Template.VP.onDestroyed(function(){
var self = this;
self.c.stop();
});
Itās important to distinguish between Blaze and React when structuring your components and managing state changes.
React works in a way that it doesnāt matter if you re-render a lot of components. The virtual DOM takes care of it and makes the small amount of changes.
But in Blaze, if you invalidate a lot of reactive variables, a lot of components will be re-rendered, sometimes without reason.
So in React, it makes sense to have only a few components getting state. This works well with most React apps, where the data is got from a call to the server and has one entry point.
But again, in Blaze and Meteor, things work differently. Data is in our own client database it can be accessed in a more āhorizontalā way. You should, actually, if you donāt want to cause a lot of unnecessary re-renders.
So, I donāt think structuring a Meteor/Blaze app with a React architecture is a good idea, unless you use Meter AND React.
If you are working with Blaze, you should get the āreactive stateā in the nearest point possible (like my example above) to avoid re-renders. This has some drawbacks, because components becomes less reusable. But to make components reusable in Blaze, you can still use the controller pattern.
If you want to structure your components in a React way, there are two interesting options:
- @timbrandinās sideburns, which is Blaze like templates but compiled to React.
- @arunoda solution: Blaze Plus which includes state/props in Blaze and minimises the renders automatically.
It looks like most of the people using Flux is going in that direction. I think it makes sense.
But no, Session was not designed for that purpose and itās not a good fit. If you store complex objects in it, it will invalidate everything and cause a lot of re-renders.
I already have a very powerful API. You can set and get pretty much anything, and it is smart enough to mix everything together and invalidate only what has changed, even with objects, arrays, MiniMongo cursors, or functions returning objects, arrays and MiniMongo cursors.
You can do stuff like this:
// save plain state, which will become reactive automatically
AppState.set('isVideoPlaying', false);
// save some state from the database
AppState.set('videoList', function() {
return Videos.find({});
});
// mix new nested state with already defined states
AppState.set('videoList.isReady', false);
Meteor.subscribe('videos', function(err){
if (!err) {
AppState.set('videoList.isReady', true);
}
});
// save complex objects
AppState.set('videoAuthor', {
name: 'Peter'
image: {
url: 'http://www.yourserver.com/images/peter.jpg',
width: 300,
height: 200
}
});
// mix it with other objects
AppState.set('videoAuthor', {
publishedVideos: 12
}
// or
AppState.set('videoAuthor.publishedVideos', 12);
// and so on...
If the state is changed, only the minimum amount of invalidations will occur. For example:
AppState.set('videoAuthor', {
image: {
url: 'http://www.yourserver.com/images/peter2.jpg'
}
});
// or the equivalent...
AppState.set('videoAuthor.image.url', 'http://www.yourserver.com/images/peter2.jpg');
wonāt invalidate things like videoAuthor.name
or videoAuthor.image.width
.
These are the getters:
AppState.get('videoList');
// => [{ id: 1, title: 'Video1' }, { id: 2, title: 'Video 2' }];
AppState.get('videoList.isReady'); // => true or false
AppState.get('videoAuthor'); // => { name: 'Peter', image: { url... }, publishedVideos: 12 }
AppState.get('videoAuthor.image.width') // => 300
Accessible as well automatically in Blaze:
<template name='VideoPlayerList'>
{{#if videoList.isReady}}
{{#each videoList}}
{{> VideoPlayer}}
{{/each}}
{{/if}}
</template>
<template name='VideoAuthor'>
Author name is {{videoAuthor.name}} and has published {{videoAuthor.publishedVideos}} videos.
</template>
<!-- or... -->
<template name='VideoAuthor'>
{{#with videoAuthor}}
Author name is {{name}} and has published {{publishedVideos}} videos.
{{/with}}
</template>
Something like this would give Meteor a simple way to have horizontal access to state, instead of the vertical approach of React.
I still want to build something with it before releasing anything. I am experimenting as well restricting the use of AppState.set only to callbacks of Flux actions, like Redux does with reducers.
Instead of exposing AppState.set
, restrict its use like this:
AppState.modify(function(action) {
switch(action.type) {
case 'PLAY_BUTTON_PUSHED':
this.set('isVideoPlaying', action.id);
break;
}
But the app I am building right now needs to be extendable via plugins, so I donāt know if too much restrictions would be possible.
Wow AppStore looks very powerful! when do you have planned to release it? Iām looking forward to beta test it
Please donāt restrict it too much or nobody will be able to use it in non-flux-canonical ways.
If I understand it basically the VideoStore is like a Mediator that:
- listens to messages that the components send through the Dispatcher
- indirectly acts upon the components by setting state reactive vars
If we were to distribute the video component we would have to distribute the VideoStore with it.
This Scenario however implies that we would have to customise the code for the VideoStore if we have one videoPlayer or multiple videoPlayers in a VideoPlayer List. I understand now the need for multiple stores in different parts of the App.
is your AppState going to have any relation with / substitute the Stores or not?
As far as I can tell from your code, the Stores both responds to the Dispatcher actions and holds the state with the reactive variables `VideoStore.getIsVideoPlaying(); what you want to do is have the stores act upon a global AppState
Is there a reason for which you would NOT put the VideoStore functionality inside of a component?
if the listening/reducer part of the stores are held inside of the component it can be distributed independently and it will work automatically because all or most of its functionality is incapsulated. This would also avoid the need for global Store Names.
(Iām imagining this because through the ComponentTree package I know that each component can search up the the chain to find the Store, or that, if the Store is actually global, each reducer function inside of the component, automatically knows which actions are generated by its children.
I saw that Dispatcher is actually an object, while I thought that it would have been a client only collection.
I think that if the redux /flux functionality would have to be built for meteor we would have to use the great stuff that Meteor already has. Publications and Subscriptions are already a part of Meteor.
If the Dispatcher or a global AppEventStore where actually a client only collection, we could get the time travel functionality too, because it would be stored in a collection.
We could even store it in a permanent collection on the server so that we could āspyā on how users actually use the app.
That is powerful.
But in order to do that we need 3 pieces of code
- a Global AppEventStore > contains all events
- a Global AppState > contains all the state
- a ComponentTree > so that each component knows which AppStore updates to listen and which to ignore
maybe the AppStore and AppState can be two sides of the same global object: according to EventSourcing standards, the state is reconstructed from the history of small state changes that are stored in the AppStore
what do you think?
There is also other discussions on the Forum on creating a redux package that I think you should chime in to tell them about your AppState
The way I understand Flux it is that the store manages the app state (collections, variables etc), very similar to what your AppState does. Maybe it doesnāt matter where the views get their ātruthā from but Iām interested in why do you differentiate between the app state and the stores? Is the main benefit that you donāt need to tie a store to each component?
I donāt think itās necessarily the stores that limit the reuse of a component, but more the data that gets sent into it; if youāre going to reuse some view, the data you send into it has to fit into the mold. This could also be done by changing the context of the view into a store helper that exposes the same type of data. Say we wanted to have a list of the top videos, and use the same template to list the top authors. This could be done like this
{{> topList data=VideoStore.topVideos }}
{{> topList data=AuthorStore.topAuthors }}
<template name="topList">
<ul>
{{#each data}}
<li>{{name}} ({{score}}) </li>
{{/each}}
</ul>
</template>
Iāve been meaning to check back in and tell you about how my store thingy is doing. Iāve mostly completed it, and will be using it in a project Iām building. I havenāt released it yet because the API hasnāt been fully finalized, but itās getting there. I have it on github at github.com/GunnarSturla/Store if anyone wants to check it out. The Store automatically registers its actions with the Dispatcher, and it has helpers that it registers with the Template, but I think it would also work great with the AppState package.
Iāve also made slight modifications to the Dispatcher in order to accept dispatches more like meteor handles Meteor.call
s. Itās a relatively small change and the dispatcher still allows dispatches to be made the āoldā way (as long as a store is set up to handle that kind of call). The dispatch call above could be made like this
Dispatcher.dispatch('play_button_pushed', 2);
This will call the action play_button_pushed
(that multiple stores could have).
Iāll continue with using the example above to show how it would work with my Store.
The VideoStore is defined like this
VideoStore = new Store('VideoStore', Dispatcher);
VideoStore.onCreated(function() {
this.isVideoPlaying = new ReactiveVar(false);
});
VideoStore.helpers({
getIsVideoPlaying: function(){
return isVideoPlaying.get();
}
});
VideoStore.actions({
play_button_pushed: function(id) {
isVideoPlaying.set(id);
}
});
You can also delay creating/initializing the store until needed by setting autocreate to false in the constructor:
VideoStore = new Store('VideoStore', Dispatcher, false);
// then, when needed:
VideoStore.create();
This calls the onCreated
functions and registers the storeās helpers and actions. You can also destroy the store, which unregisters it from both the Dispatcher and Template by calling VideoStore.destroy();
. Thereās more on how it works in the readme on GitHub.
Iām planning on publishing the package soon but it needs the changed version of the dispatcher, do you (@luisherranz) want me to submit a pull request? If youād rather keep as it is, Iāll just include it in my package (or submit the fork)
Iām not quite sold on the need for an AppState, but I can see these three packages work well together and ease the use of Flux in Meteor.
Yeah, basically thatās it.
It also has the benefit of being able to store or send that object-tree or implement undo & redo options (interesting for debugging as well).
I have published the AppState package although it is still under development. If you want to check it out docs are here.
Yeah, sure. You could use the #with helper as well, for example. Blaze is great!
{{#with VideoStore.topVideos}}
{{> topList }}
{{/with}}
{{#with AuthorStore.topAuthors}}
{{> topList }}
{{/with}}
I think itās great. As I told you, it has been a great inspiration. Let me know when itās ready and I will mention it in the MeteorFlux documentation.
I have just made that modification myself. Both in the dispatch and the register methods. Check the new Dispatcher documentation.
I have added a template helper to dispatch without JS as well:
{{#each posts}}
<h1>{{title}}</h1>
<p>{{content}}</p>
{{#if favorite}}
<button
data-action-type='UNFAVORITE_THIS_POST'
data-action-id={{_id}}>
Remove from favorites!
</button>
{{else}}
<button
data-action-type='FAVORITE_THIS_POST'
data-action-id={{_id}}>
Add to favorites!
</button>
{{/if}}
{{/each}}
Itās a new package called Dispatcher Helper. Check the docs here.
Let me know if the new version suits your needs. If it doesnāt, we will modify it, donāt worry.
Sure! I think all them can be great tools.