Redux for Blaze Example App


#1

After using Redux for a few days I wanted to integrate it with Blaze. Meteor doesn’t offer any structure or methodology for keeping track of local app state (just the tools). Redux can help with this by offering the contracts and a little glue. It’s mostly just regular functions.

This app is really too small for it but is simple to grok. Think about maintaining an ecommerce site or a Spotify app. This is where it can be helpful.

You could store minimongo state in this Redux system but for Blaze it makes more sense to use it for local state not persisted to the database.

https://github.com/AdamBrodzinski/meteor-redux-example

Example flow with Blaze:

  // 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>
      <div>{{store 'selectedPlayerName'}}</div>
    </div>
  {{else}}
</template>

Here’s a guide on using Redux: http://rackt.github.io/redux/


Any guide on application architecture for enterprise-scalable applications?
Possible to access subscription data without declaring collection?
#2

@awatson1978 have you had a chance to look at Redux in general? I’m thinking it might be a better alternative to using template vars and Session for larger Blaze apps.

It looks even better than flux with a more functional/Elm inspired way to do the same thing as flux. The cool part is it’s pretty view agnostic.

I’m not sure how I feel about the API with reactive-dicts but it seems to be ok. It feels more crufty than using Redux with vanilla objects/arrays. Also unfortunate that store is global because we don’t have modules… but if you don’t use set on that global and only use actions to mutate data it could still be useful.


#3

Hey there,
Haven’t dived deep into Redux, as I’m only just now refactoring our ActiveEntry templates into their own package, and refactoring from Session to template level ReactiveDicts.

But now that we’ve got our QA strategy in place, we’re refactoring the hell out of our packages and apps, and things are looking great. We’re in a far better place to start looking at these kinds of patterns.

I’m likely to experiment with Redux and a dispatch pattern on one of the Clinical Track demo apps. We’ve got three planned right now… ClinicalTrials (which uses AutoForms), SocialHealthRecord (which will probably use socialize:friendships), and ChecklistManifesto. I’m thinking ChecklistManifesto might be a good candidate for Redux, since it will let external packages register actions and events with the checklists and workqueues.

I’ll post the ActiveEntry package and reference app in a bit, and do a bit of a compare/contrast with where things stand right now, and how a Redux/Flux pattern might apply.

Some of Redux I totally want to incorporate, some of it seems like we’re simply using different terms for the same things, and some of it… well, some of it we just seem to have very different understandings of what basic terms mean and what different parts of the architecture are meant to be used for! :smile:

Will post more later tonight/tomorrow.


#4

I totally agree :+1:


> Some of Redux I totally want to incorporate, some of it seems like we're simply using different terms for the same things, and some of it...

Yep I think it’s basically just borrowing ideas… he mentioned the languages, frameworks, and patterns it was inspired by but I can’t remember offhand. I think a lot of the naming comes from functional programming like reducers are essentially a reduce function.

If you do give Redux a shot, watching this before hand was a great primer for me (and a look at the terrific devtools for it) https://www.youtube.com/watch?v=xsSnOQynTHs


#5

Yes, he list “inspired by” on Reduxs repo too


#6

@awatson1978 did you ever come back to this ? Will be very interested to know that what you think about using these flux like architecture with our clinical meteor track.


#7

@kaiyes @awatson1978 20/20 hindsight I think the ideal thing for Blaze would be to use the Redux pattern but not exactly use Redux unless you have a good use case for middleware. Using Redux works but it feels like more ‘work’.

I typed out an idea for this in some earlier posts and cant find it :confused:

But basically my idea was for most apps it makes sense to not use reducers because most people are worried about lines of code and the extra layer of complexity.

In that example the UI would just be calling an ‘action function’ without dispatching anything. Then in that func it would call get/set on the global state (which logs state in debug). This essentially just helps us keep local data in one place (global dict) and decouples business logic from the UI, which is a step in the right direction.

Then in larger apps you can add the ‘reducer’ layer that would do the actual mutations… this lets you perform side effects in the ‘action functions’ prepare data, and then hand off to the reducers.

A lot of this hinges on how performant updates are with a single reactive dict… if one change makes the entire tree trigger a change then this could be bad for Blaze perf.


#8

u mean this? Is there really any need to use Flux in Meteor?


#9

Not that one, thought that does explain it a lot better.

Here’s an example without the ‘reducer’ layer. It also uses a ‘dispatch’ which for simple apps just logs in debug or if you need more structure the dispatch could be used to send the action to a ‘reducer’ layer (not in example). The reducer layer can also add the ability to disallow 'set’s on state unless they’re dispatched.

const _store = new ReactiveDict('store');

store = {
  get(key) {
     console.log('Store', key)
    _store.get('key');
  }
  set() {
  ...
  }
};


// on small apps dispatch could just log user event stream in debug
dispatch = function(...args) {
  if (__DEV__) {
    console.log(args);
  }
}


Actions = {};
// this example is just being saved to memory, not mongo (though you could here)
Actions.createPost = (post) => {
  const posts = state.get('postsData');
  posts.push(post);
  state.set('postsData', posts);
  analytics.track('add post');
  Mailer.sendNewPostEmail(post.name)
  return {type: 'CREATE_POST'}
};



// somewhere in view (destructure action module)
const {createPost} = Actions;

Template.PostsPage.events({
  'click .add-post': function() {
    createPost({name: 'test1', desc: 'foo bar'});
  },

  'click .add-post2': function() {
    // or auto log with 'dispatch'
    dispatch(createPost({name: 'test1', desc: 'foo bar'}));
  },
});

Confused With Meteor's Best Practices: the Way They Are Now & the Way They Used to Be