MeteorFlux Flow

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>

1 Like

@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.

2 Likes

Hello,
thanks for the great and inspiring work.
what is the State of your ā€œAppState packageā€?? (pun intended :smile:)
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:

  1. All the reducers that are listening to that event will respond, which might be either in the same Playbutton, or in all VideoPlayers
  2. 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)
  1. 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 :slight_smile: )?
Is state always derived from the EventStore (this is somewhat confusing to me)?

:slight_smile:

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.


3 Likes

Wow AppStore looks very powerful! when do you have planned to release it? Iā€™m looking forward to beta test it :smile:

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

  1. a Global AppEventStore > contains all events
  2. a Global AppState > contains all the state
  3. 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 :smile:

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.calls. 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! :smile:

{{#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.

Here you go: MeteorFlux AppState

It doesnā€™t depend on Dispatcher, so you can use it without Flux.

If I add more stuff, it will be with options, so it wonā€™t affect the default behaviour.

It is difficult to answer. If you are using an object-tree, then the Stores as ā€œclassesā€ lose sense. They become just functions which mutate state. Redux does a good job explaining this.

In Flux views should only render the app state. But as many things, it depends on the component and how it is going to be used. I think unless you want to share it with the rest of the world like a black-box component, then logic should go outside.

If different parts of your app are going to be able to play, pause or stop videos (for example) that functionality should be in a Store which controls their state. The component should only care about the rendering part, it shouldnā€™t care if a menu has been opened and the video should pause, for example. Only -> state: ā€œpausedā€. Then -> state: ā€œplayingā€.

I was glad to see this post, it reminded me I wanted to take a look at flux

so I made a very basic pattern to do flux in the meteor way and explain stateful declarative approach to those who are only just getting there

http://fluxstate.meteor.com/

thatnks @luisherranz for your meteorflux packages - they make things easy

No problem :smile:

Thatā€™s really great, thanks for contribution. Iā€™d have a couple of comments if you want my opinion.

hey @luisherranz, Iā€™d really value your opinion

I love it so far, especially this:

// Init app
const handle = Meteor.subscribe('user');
AppState.set('initializing', function() {
  return !handle.ready();
});
1 Like

Thatā€™s neat. Good example!

With the new Dispatcher version (1.1.0) the dispatcher accepts an string as first argument:

Template.circuit.events({
        'click #switch' : function () {
            Dispatcher.dispatch('CIRCUIT_TOGGLE');
        }
    });

Note that it will be added to type instead of the old actionType (yep, we switched to Redux style, itā€™s simpler).

For Stores, I wouldnā€™t create them inside Templates. I will create a folder and individual files for each one of them.

Then, inside stores/circuitStore.js I would do:

// Set default state needed by this store using the MeteorFlux AppState package.
AppState.set('circuit.closed', false);

Dispatcher.register(function(action) {
  switch (action.type) {
    case 'CIRCUIT_TOGGLE':
      let currentState = AppState.get('circuit.closed');
      AppState.set('circuit.closed', !currentState);  // reverse state.
      break;
    }
});

// Link states using Tracker
Tracker.autorun(function(){
  let meta = AppState.get('circuit.closed') 'light on' : 'light off';
  AppState.set('circuit.meta', meta);
});

Note here we changed to payload.actionType to action.type. Again, itā€™s Redux style.

Then, with AppState you donā€™t need to create a helper anymore. You can just use {{#if circuit.closed}} and {{circuit.meta}} wherever you want.

Doing it this way the code you need is reduced significantly.

If you want to test this code just dispatch "CIRCUIT_TOGGLE" and test the state with AppState.get. You donā€™t need access to the function itself.

1 Like

Oh, and if you use the new Dispatcher Helper package you can dispatch directly from Blaze.

This will work exactly like your code:

<a href='#' data-action-type='CIRCUIT_TOGGLE'>
  <svg width="600" height="400" id="svg2">
        ...
  </svg>
</a>

: )

I was working from previous stuff Iā€™d done, and I stuck your dispatcher and AppState into what Iā€™d roughly be doing with my own solution. Part of my thinking in putting them inside the template,

  • architectural simplicity, fitting into the exiting Meteor model that everyone knows
  • ensure they are available when needed
  • access to the template instance
  • to use other aspects, e.g. data model, parents and so on (although this may be wrong headed in terms of flux)
  • also, to lock the store into the template

So, iā€™d be interested in your thoughts about that.

Quick question on this: this is defined globally, correct? Is there any way to have app states that are specific to a particular store?

If you are using react with meteor, is it recommended to still use session variables or just use the original redux since it is react compatible?