MeteorFlux Flow

This is interesting because Flux was not designed with routers in mind.

Why did you come to that conclusion? Did you try other approaches?

I have added user accounts and a few other things:


http://cartflux.meteor.com

  • User Accounts, with a new store (UserStore) to control logins, account creation, errorsā€¦

  • UserActions, for login/logout actions. This pattern is suggested by Facebook for async actions:

For example, instead of dispatching the actions in the Template events, you use the UserActions:

Template.LoginForm.events({
  'submit #login-form': function(event){
    event.preventDefault();
    UserActions.login(event.target.user.value, event.target.password.value);
  },

and thenā€¦

UserActions = {
  login: function(user,password){
    Meteor.loginWithPassword(user, password, function(error){
      if (error) {
        Dispatcher.dispatch({ actionType: "LOGIN_FAILED", error: error });
      } else {
        Dispatcher.dispatch({ actionType: "LOGIN_SUCCEED" });
      }
    });
  },

This is only for async actions.

  • Library to validate inputs in the client. This pattern is suggested by Facebook as well.

To do things like this:

UserActions = {
  createAccount: function(user){
    if (!Libs.Validations.areValidPasswords(user.password, user.retry_password))
      var error = new Meteor.Error("password-doesnt-match", "It looks like the passwords don't match.");

    if (error)
      Dispatcher.dispatch({ actionType: "CREATE_ACCOUNT_FAILED", error: error });
  • Refactor of the Stores, so now they can be unit tested. I added dependency injection as well.

Itā€™s very simple, instead of creating the object, you create it inside a function and return it:

// Creator
var newOneStore = function(SomeCollection){

  // Reactive Vars
  var something = new ReactiveVar(false);

  // Object
  var OneStore = {
    // Callbacks...
    onSomethingHappened: function(){
      something.set(true);
      SomeCollection.insert({});
    },

    // Getters...
    getSomething: function(){
      return something.get();
    }
  };

  OneStore.tokenId = Dispatcher.register(function(payload){
    switch(payload.actionType){
      case "SOMETHING_HAPPENED":
        OneStore.onSomethingHappened();
        break;
    }
  });

  return OneStore;
};

// Create the instance
SomeCollection = new Mongo.Collection('some-collection');
OneStore = newOneStore(SomeCollection);

Of course you can use this pattern or not.

Just wanted to say, this is my first time visiting forums.meteor.com, on this thread, and itā€™s just amazing. A great showcase of Meteor. Sorry for hijacking! :slight_smile:

1 Like

@luisherranz thank you very much for this. It has been extremely educational. Iā€™m still ramping up on both React/Flux and separately Meteor. Any chance you can help me understand how what you propose above is different than the MVVM pattern provided by the ViewModel http://viewmodel.meteor.com. Thanks!

@manuel did a great explanatory video. Here:
https://www.youtube.com/watch?v=5LsTUv61cKU

.

MVVM is another design pattern, invented by Microsoft and useful as well. This is a very simplistic image but you can get the idea:

As you can see MVVM is two-way-data-binding and Flux is one-way-data-binding. Flux is this way on purpose so applications are easier to debug.

One of the more important things @manuel is trying to solve with his package is the fact that in Meteor you put logic in the Template Events and that is highly coupled to the UI and harder to test. In MVVM the View has references to the ViewModel but the ViewModel doesnā€™t know about the View so it is more decoupled and easier to test. This is very well explained in the video.

In this regard, MVVM is good, but Flux solves it as well: When there is an event it just dispatches an action. Then, the stores which need to do something about it, listen and react. All the logic is in the Stores and they are decoupled and easy to test. Controller-Views (Meteor template helpers and events) only dispatch actions and retrieve data.

I prefer the Flux approach because I really like working with notifications. They are more decoupled, easier to follow and easier to maintain.
The problem with notifications is that they can get very messy and unpredictable when they are chained. I had problems in the past with this. Flux doesnā€™t allow to do it, so I liked the pattern and wanted to give it a try.

Iā€™m no expert on MVVM so if anybody wants to jump in and correct me, is welcomed :blush:

Thank you for the helpful response!

1 Like

I have removed the insecure package.

No big deal, just had to change things like this:

Cart.update(id, {$inc: {quantity: 1}});

to things like this

Meteor.call('CartStore.increaseCartItem', id);

// then...
Meteor.methods({
    'CartStore.increaseCartItem': function(id){
      Cart.update(id, {$inc: {quantity: 1}});
    }
});

For this, I donā€™t like the whole allow and deny stuff. I think Meteor.methods are easier to understand.
And as they are simulated on the client, you still get latency compensation.

http://cartflux.meteor.com/

I am working with pagination and I noticed that our subscription system and Minimongo are concepts not covered by Facebookā€™s Flux.

Who should decide which content is available in the client at each moment?
View or Stores? Any opinions?

1 Like

I have updated the example using pagination.

It was easy thanks to Meteor reactivity but I saw some new things. For example, I need to be sure the actual page is less than the total number of pages. If you are in the last page and you delete all products there, you can find yourself in a blank page. User should be redirected to the new last page.

You can check the total number of pages each time you remove a product, but that doesnā€™t take advantage of Meteorā€™s reactivity. If instead of that, you can use Tracker.autorun to ensure this is checked each time the total number of pages changes, no matter what, who or when.

Tracker.autorun(function(){
    var total_pages = CatalogStore.getNumberOfPages();
    var actual_page = CatalogStore.getActualPage();
    if (actual_page > total_pages) {
      Session.set("CatalogStore.actualPage", total_pages);
    }
  });

This seems great because it is decoupled and automatic. But right now itā€™s a lonely piece of code inside the Store. It doesnā€™t belong anywhere, really.

What do you think?
Can you run into problems if you use it?
Can it save you from running into problems?
Where should we put it?

http://cartflux.meteor.com/

Thereā€™s another thing I have noticed which is different from Meteor and Facebook Flux: the waitFor.

Due to Meteorā€™s reactivity, I looks like I didnā€™t need to use waitFor even once yet.

They explain its use with this example:

 // Keeps track of which country is selected
 var CountryStore = {country: null};

 // Keeps track of which city is selected
 var CityStore = {city: null};

 // When a user changes the selected city, we dispatch the payload:
 Dispatcher.dispatch({
   actionType: 'city-update',
   selectedCity: 'paris'
 });

 // This payload is digested by `CityStore`:
 Dispatcher.register(function(payload) {
   if (payload.actionType === 'city-update') {
     CityStore.city = payload.selectedCity;
   }
 });

// When the user selects a country, we dispatch the payload:
 Dispatcher.dispatch({
   actionType: 'country-update',
   selectedCountry: 'australia'
 });

 // This payload is digested by both stores:
 CountryStore.dispatchToken = Dispatcher.register(function(payload) {
   if (payload.actionType === 'country-update') {
     CountryStore.country = payload.selectedCountry;
   }
 });

 CityStore.dispatchToken = Dispatcher.register(function(payload) {
   if (payload.actionType === 'country-update') {
     // `CountryStore.country` may not be updated.
     Dispatcher.waitFor([CountryStore.dispatchToken]);
     // `CountryStore.country` is now guaranteed to be updated.
 
     // Select the default city for the new country
     CityStore.city = getDefaultCityForCountry(CountryStore.country);
   }
 });

So you use waitFor when you need to wait for another store.

But in Meteor you can use reactive variables, so you will do something like this:

 // Keeps track of which country is selected
 var CountryStore = {};
 CountryStore.country = new ReactiveVar();

 // Keeps track of which city is selected
 var CityStore = {};
 CityStore.city = new ReactiveVar();

 // When a user changes the selected city, we dispatch the payload:
 Dispatcher.dispatch({
   actionType: 'city-update',
   selectedCity: 'paris'
 });

 // This payload is digested by `CityStore`:
 Dispatcher.register(function(payload) {
   if (payload.actionType === 'city-update') {
     CityStore.city.set(payload.selectedCity); // set reactive value
   }
 });

 // When the user selects a country, we dispatch the payload:
 Dispatcher.dispatch({
   actionType: 'country-update',
   selectedCountry: 'australia'
 });

 // This payload is digested ONLY by `CountryStore`:
 Dispatcher.register(function(payload) {
   if (payload.actionType === 'country-update') {
     CountryStore.country.set(payload.selectedCountry); // set reactive value
   }
 });

 // In Meteor we can use Tracker.autorun instead of Flux waitFor
 Tracker.autorun(function(){
   var country = CountryStore.country.get();
   CityStore.city.set(getDefaultCityForCountry(country));
 )};

Whenever CountryStore.country changes, Tracker.autorun will rerun that function and CityStore.city will be always in sync, even if country has changed by a different event than country-update.

I still wonder how good is this, because it looks very good to me right now.
And I wonder if waitFor would be still useful in Meteor in any case at all.

1 Like

I have included FlowRouter in the shopping cart example.

What I have done is very simple yet, but it looks promising.

I have added a new ā€œkind of storeā€ called CatalogRouter. Itā€™s not called Store because I thought it was too long (CatalogRouterStore) but in a sense, is just another Store which takes care of the Catalog routing.

FlowRouter helps converting url requests to actions:

FlowRouter.route('/products/:page', {
  action: function(params) {
    Dispatcher.dispatch({ actionType: "GO_TO_PRODUCTS_PAGE", page: params.page});
  }
});

After that, the Store takes control. It has useful get methods for the Views, like getActualPage(), getNumberOfPages() or getNextPageUrl(). The views consume them to render the correct pagination, urls, etc.

In my last reply I was talking about using Tracker.autorun to keep coherence. This Store is in charge of that as well. For example, it checks reactively if the user is in a page which doesnā€™t exist anymore and solves the problem.

Andā€¦ thatā€™s it.

This is the new Router-Store file:

http://cartflux.meteor.com/

Next step: start messing with the layout and see how Flux, FlowRouter and FlowLayout fit together there.

PS: I have also uploaded it to scalingo because I want to test it. So far so good. Super easy to deploy:
https://cartflux.scalingo.io/

2 Likes

That is some awesome content :blush: keep posting!

1 Like

Some news here.

At this point, I wanted to include dependency injection in the stores (so they are easier to test). At the same time, I run into problems with loading order, globals and circular dependencies.

I have fallen in love with Meteorā€™s reactivity lately, so I thought, why not to apply it to this problem: I created a package called Reactive Dependency:


I did a very simple example with some stores depending on each other:

It can be used without Flux as well.

Back to the Shopping cart example, these are the things I have done since the last update:

  • I have included the Reactive Dependency package and itā€™s working great. No more loading order or globals problems.
  • I have done some refactoring of the code after reading the Meteor Style Guide.
    Now I am using var self = this; everywhere and new to create my Stores.
  • I have added a MainRouter which is in charge of retrieving routes.
  • I have added a MainLayout which is in charge of changing the layout. I am using FlowLayout now. Working great.
  • I have created client, both and server folders and move things where they belong.
  • client: views, stores, layouts, routers.
  • both: methods and collections.
  • server: publications.
  • I have added what I call a ā€œLook aheadā€ publication.

What I mean for a ā€œLook aheadā€ publication is that it is subscribing to both the previous and next page, instead of only the current page. That means when you go to the next page, the data is already there so it feels realtime.

I am still trying to figure out some things:

  • Who should do subscriptions. Templates? Stores? Routers?
  • Are routes ā€œApp Stateā€, ā€œUser inputā€ or a mix between both?

If routes are App State, they should be retrievable from Stores. But if they are ā€œUser Inputā€ they should dispatch actions.

I have been thinking about the ā€œEverything is a packageā€ pattern as well. And I think itā€™s great. Meteor itself, Telescope or Atom look are like that. When things start to get complex, itā€™s better to create a package. So, for example, the ā€œLook aheadā€ publication probably deserves its own package. But then you can use Flux to organise all the pieces together: keep the App State of your app and populate your Views.


http://cartflux.meteor.com/
https://cartflux.scalingo.io/

I just find this thread and Iā€™m very impressed. Iā€™d love to see how this can be tested with velocity. Would you mind to add tests on your cart repository ?

1 Like

Yes, I wanted to add dependency injection first. So itā€™s my next step : )

But I am still trying to figure out the best way to deal with routes and layouts as well.

1 Like

I have done another example of you how to pass data between templates for another thread.
Itā€™s very simple but here it is:

http://meteorpad.com/pad/36dwXz9ktQK3SJGgB

And this is the thread:

Maybe I donā€™t understand your use-case entirely, but we use state-machines for this kind of thing (ecommerce, order state, complex workflows that touch multiple APIs). We find state machines really help manage the complexity.

Here is a Meteor wrapper for a state machine we are using:

1 Like

Thanks for the contribution, @maxhodges

As I see it, a finite state machine is a very useful tool, but itā€™s not the same use-case here. Flux is a software architecture which replaces MVC.

I coded a very complex state machine and I used it extensively with PureMVC. It was based on chained notifications. It worked well but it was hard to debug. Flux doesnā€™t allow chained notifications for that reason.

In order to work within Flux I think the workflow would be like this:

1.- UI still dispatches actions, for example: ā€œUSER_CLICKED_PLAY_BUTTONā€.
2.- Stores register for those actions, and can change the State Machine state. For example: PlayerStateMachine.play().

Now the obvious next step would be:

3.- The State Machine changes its state and dispatch an action with the new state, so Stores can act. For example: ā€œMEDIA_PLAYER_STARTED_PLAYINGā€.

Problem with that: you got a chained notification, because ā€œUSER_CLICKED_PLAY_BUTTONā€ hasnā€™t finished yet. No good.

The only solution which comes to my head right now is to make the current state of the State Machine part of the ā€œApp Stateā€. In other words, make the state reactive:

3.- The State Machine changes its state, which is reactive. Anything depending on that state recomputes thanks to Tracker.

Image PlayerStateMachine.getState() retrieves a reactive variable with the state. Then UI can update like this:

Template.PlayerView.helpers({
  playClass: function () {
    if (PlayerStateMachine.getState() === "play")
      return "playing";
    else
      return "not-playing";
  }
});

And Stores can update like this:

// Inside PlayerStore
Tracker.autorun(function(){
  var state = PlayerStateMachine.getState();
  if (state === "play")
    mediaPlayer.play();
  else if (state === "pause")
    mediaPlayer.pause();
  else
    mediaPlayer.stop();
});

Any thoughts?

1 Like

I have added some tests to the Catalog Store of the Shopping example:
https://github.com/meteorflux/cartflux/blob/master/tests/jasmine/client/unit/CatalogStoreSpec

Iā€™m using Jasmine which has unit testing in the client. Iā€™m not sure if you can use Mocha as well.

Iā€™m testing only the Stores because most of the logic should be there. Tests look like this:

it('should add a product to the Catalog Collection', function(){
      Dispatcher.dispatch({
        actionType: "ADD_PRODUCT",
        product: { name: "Test", price: 12345 }
      });
      expect(Meteor.call).toHaveBeenCalledWith(
        "CatalogStore.addProduct",
        { name: "Test", price: 12345 }
      );
    });

it('should not return any prodcuts in this search', function(){
      Dispatcher.dispatch({
        actionType: "USER_HAS_SEARCHED_PRODUCTS",
        search: "Test nonexistent product"
      });
      expect(catalogStore.getSearchedProducts().fetch())
        .toEqual([]);
    });

This is very similar to what Facebook is doing with Jasmine and Jest:
http://facebook.github.io/react/blog/2014/09/24/testing-flux-applications
(I donā€™t see the need for Jest in Meteor, by the way)

Facebook recommends to move as much logic as you can to the stores, so it can be tested easily. Remember: Your Views should only dispatch actions and retrieve data from Stores.

Maybe integration tests are not needed because views are very simple and you are covered with unit tests for Stores and some kind of End-to-end tests for the whole application. Anyway, Iā€™m not an expert on testing so any suggestions are welcomed!


Besides, I have done some refactoring of the Router, Layout and Store structure.

This is how a Store looks like now:
https://github.com/meteorflux/cartflux/blob/master/client/stores/CatalogStore
As you can see, the only public methods now are the getters.

This is the Router:
https://github.com/meteorflux/cartflux/blob/master/client/routers/MainRouter
Which is used to dispatch actions and retrieve routes.

And the Layout:
https://github.com/meteorflux/cartflux/blob/master/client/ui/layouts/MainLayout
Which only renders the correct layout when it receives an action.

Of course, you can use your favourite structure in your own Flux app. Only the dispatcher is needed.

2 Likes

Iā€™ve just added some tests for the Cart Store as well:
https://github.com/meteorflux/cartflux/blob/master/tests/jasmine/client/unit/CartStoreSpec

Some examples of tests:

it('should remove a cart item if decreasing and quantity is 1', function(){
      spyOn(Cart, "findOne").and.returnValue({ quantity: 1 });
      Dispatcher.dispatch({
        actionType: "DECREASE_CART_ITEM",
        item: { _id: 123 }
      });
      expect(Meteor.call).toHaveBeenCalledWith(
        'CartStore.removeCartItem',
        123
      );
    });

it('should retrieve the userId as the cartId when the user is logged in', function(){
      spyOn(Meteor, 'userId').and.returnValue(123);
      Dispatcher.dispatch({ actionType: "LOGIN_SUCCEED" });
      expect(cartStore.getCartId()).toBe(123);
    });

Very straightforward.

The only issue was with ReactiveDict. If you want it to survive to Hot Code Pushes, you have to give it a name. If you try to create a second instance of your object, it throws a Duplicate Name error. I donā€™t know yet how to solve that.

1 Like