Is flux worth using. When is it good or bad to use

I am wondering weather I should learn and use it.

2 Likes

So iā€™ve asked myself that a lot over the past few weeks. Hereā€™s my experience.

Flux solves a couple of problems.

  • Keeping all of your app state in sync (local and remote)
  • Decoupling your UI from data/business logic
  • Designing a simple mental model of how data is flowing
  • One way data flow (as opposed to Angularā€™s 2way)
  • easy to come back months later and ā€˜can jump right inā€™ (easy mental model)
  • a defined system (great for teams)

The largest con is the heavy learning curve. This is caused by people having bad examples with overly complicated verbiage. It could be bad if you need to onboard inexperienced devs and/or remote devs. Though having someone in person to walk through and answer questions speeds up learning 100x

If you search for flux on here I have many ramblings on it :laughing: For me itā€™s a great way to structure your app. Data fetching aside, it makes it maintainable.

Sure you could write this and call it a day:

Template.posts.events({
  'click .upvote': function(e) {
    Session.get('bar');
    Votes.update(.....);
  }
});

but it will crumble with enough time and iterations. Itā€™s also so difficult to test that you likely wonā€™t test it. Iā€™ve learned this the hard way.

Itā€™s much better to decouple the view and keep things organized like this and let the view gather up the least bit possible and hand it off:

// templates.js

Template.posts.events({
  'click .upvote': function() {
     dispatch(Actions.upvotePost(docId))
  }
});

// actions.js

Actions.upvotePost = function(docId) {
  Posts.update(...);
  var newDocs = Posts.find().fetch();

  return {
    type: 'UPVOTE_POST',
    newData: newDocs
  };
};

If you are interested in picking a flux library I would highly recommend looking into Redux. I just open sourced a new branch in the react leaderboard repo tonight here

2 Likes

Asking questions (to learn, not to challenge!) :slight_smile:

Why?

I assume you meant upvotePost?

Iā€™m gonna be diving into React for the first time this weekend. Pretty stoked! Iā€™m sure at some point Iā€™ll start thinking about whether I need to use Flux or Redux.

1 Like

Thanks this helps a lot!

1 Like

Well, redux is interesting too, storeless Flux.
Especially in Meteor it feels like store is just copy of minimongo plus some custom values.
In other stacks I feel like fetching data to store itself, but we in Meteor already have them on client side all available.

Using minimongo and latency compensation by calling client side stub methods could be the Meteor way of updating data for example used in redux.
Some deep look into MDGs react mixin reactive source code could potentionally inspire someone to abstract it into separate package which would work as easy meteor reactive source action emitter. Focusing all these reactive invalidations to 1 place where we can log and maybe time travel? While using native stores provided in Meteor.

I would love to use such Redux implementation :smiley:

1 Like

So this looks just like another syntax for Meteor methods. Whatā€™s the difference?

1 Like

Great glad it helped :smiley:


[quote="fvg, post:6, topic:8442, full:true"] So this looks just like another syntax for Meteor methods. What's the difference? [/quote]

Flux is a generic architecture to help structure how data flows throughout your app. Meteor methods are a way to call methods on a remote server to get a result.


[quote="ffxsam, post:3, topic:8442"] > but it will crumble with enough time and iterations.

Why?
[/quote]

Good question. This is hard to really explain without good code examples but here is my experience. The end result is playing a game of ā€˜whack a moleā€™. (also to answer your other question, youā€™re correct, it should have been upvotePost, now fixed)

A team of 2 or 3 people working on the same code base get a bug report and one of them has to go into the app to fix it. One thinks they fixed it but all of a sudden another part of the app breaks, so you fix that, then another part breaks. Finally itā€™s fixed. Then a new feature is brought in, now you have to go and modify one of those other areas but this time thereā€™s a bug because you couldnā€™t see what the ā€˜wholeā€™ app as doingā€¦ you could only see that little slice for the new feature.

Things like calling update or insert in the click handler work, until you have another part of the app that updates the same thing (duplicated code). Itā€™s much harder to see where the updates are because they could be in any template js file.

In contrast if you have the update/insert calls in a single placeā€¦ you know where to look and the single origin can be shared in many templates if needed.

In the example I used Session to get some kind of app state to presumably make a control flow decision. This works great at first. However using a global state that is set all over the app makes it hard to see when/where it changes. The global part itself isnā€™t horrible (I mean flux is global state after all), but itā€™s mutation of global state without any order that creates an issue.

In that example someone else could have changed session before it checks the condition and you didnā€™t know it (or if you yourself forgot). This could cause a nasty bug.

Lastly the example is really hard to test. No longer can you just spy on the action being calledā€¦ you also have to check business logic and database calls. Personally I skip testing it and say to myself iā€™ll catch it with an acceptance test (which is brittle). If you just spy on the action being called you can test business logic somewhere else (the action creator or an equivalent ā€˜placeā€™). The UI changes a lot the business logic changes less.

If you can decouple frequently changing code from unfrequent changing code itā€™s generally a win

Does this make sense? I think a lot of it is hard to see unless itā€™s bitten youā€¦ iā€™ve certainly wrote my fair share of technical debt :laughing:

3 Likes

I thought this myself as well. However once you look into it they donā€™t overlap that much, especially with Redux.

There is 0 overlap with local state that is not stored on the server and it replaces the use of Session and Rective Dicts and Template vars. For instance you can save form input data so that when it hot-reloads it doesnā€™t go away, or when going from page 1, 2, 3, 4 and then back to 3 or 2, you can retrieve this state and update the forms values.

With collections there is overlap. At best you have two copies of the same array in memory. However Redux doesnā€™t do anything to query, sort, or most importantly invalidate and update the cache. This is a hard problem. With Meteor weā€™re lucky to just Foo.find() and we have the latest state. We can use this to our advantage to just do this (in Redux):

    case 'INCREMENT_SCORE':
      return Players.find();

instead of this (this is even with ES6 spread syntax):

    case 'INCREMENT_SCORE':
        // make a copy of state and insert new doc
        return Object.assign({}, state, {
          todos: [...state.todos, {
            text: action.text,
            score: state[3].score += 1
          }]
        });

and then we still have to keep track of dirty bits if we would have to roll our own latency compensation. Luckily we have this for free in Minimongo!

However itā€™s worth mentioning that if you wanted to use Redux you can still use it for local state and then use Minimongo as usual for database state. This makes it harder to reason about since you have two sources of data, but there isnā€™t a memory cost.

Using minimongo and latency compensation by calling client side stub methods could be the Meteor way of updating data for example used in redux.

This is basically what I did with the Meteor Redux React repo here:

Actions.incrementScore = function(playerId) {
  Players.update({_id: playerId}, {$inc: {score: 5}});
  // we don't return and instead let collection emit 'changed' action to update store
  return { type: 'INCREMENT_SCORE' };
};

// any time collection changes emit action to update store
trackCollection(Players, (data) => {
   store.dispatch( Actions.playersChanged(data) );
});


[quote="shock, post:5, topic:8442"] Focusing all these reactive invalidations to 1 place where we can log and maybe time travel? [/quote]

I have central logging done but am working on the time travel dev-tools. This will log every action that happens in the app using Redux middleware (unless turned off)

// console.log our state changes
logger = store => next => action => {
  log('[Dispatching]', action);
  // essentially call 'dispatch'
  let result = next(action);
  log('[Store]', store.getState());
  return result;
};

I'm also working on Redux ***for Blaze*** at the moment. Large Blaze apps need structure. Redux is a great fit as it's platform agnostic. I'm not sure how well it'll work with database state but at the least it's great for local state... and is still fully reactive. You can just use: `store.getReactive('selectedName')` or the helper: `{{store 'selectedName' }}` and it will update the Blaze template like normal.
1 Like

Thanks for the detailed response, @SkinnyGeek1010! Makes sense!

I was browsing the above codeā€¦ just to add some ES6 geekiness to the mix, instead of using 'INCREMENT_SCORE' string constant, why not use symbols?

const INCREMENT_SCORE = Symbol('Score increment action');

Then you could just refer to it as a constant throughout the code, rather than a string.

1 Like

@SkinnyGeek1010 Iā€™ve struggled with flux myself. I understand how it works, but I donā€™t see how it benefits my app in most situations. For instance, I never use insert/update calls on the client. I always use a meteor method because then I donā€™t have to patch security holes with allow/deny rules (which I think is the correct way to structure meteor code anyways). The meteor method takes the role of an ā€œactionā€ but is already a native piece of Meteor. In addition, if I need to change the way something works I only have one place to change it - the method. Thatā€™s just one example, but I havenā€™t really found flux to be of much use in the apps Iā€™ve built. Maybe my opinion will change at some point, but I still donā€™t think itā€™s necessary for most Meteor apps if they are structured right.

Yep that would work! I do like ES6 :smiley:

I chose to try and use as little as possible so that people who havenā€™t had a chance to use ES6 arenā€™t lost. Some areas like the middleware are so verbose that I chose to use it there.


Checkout this Redux exampleā€¦ I think itā€™s way easier to understand than flux (Redux tech. isnā€™t flux), does this make any sense? Itā€™s basically just a way to keep local state in one place in one object (store):

If youā€™re still interested in Redux, thereā€™s a quick tutorial on how to make it work: http://rackt.github.io/redux/index.html


Yep I use Meteor methods too. Actually, using Posts.insert is just a facadeā€¦ itā€™s actually doing Meteor.call('posts/insert', ...) and making stubs for you. I tend to make my own facades as well.


[quote="ryanswapp, post:10, topic:8442"] The meteor method takes the role of an "action" but is already a native piece of Meteor. [/quote]

Meteor methods are low level tools to help you make a remote function call on the server. They also allow latency compensation if a stub is used. Meteor methods should be used inside action creators to get the data you need. I also think itā€™s unfortunate that ā€œactionā€ and ā€œaction creatorā€ sound so similar theyā€™re used interchangeably

Actions are just simple objects that tell the app ā€˜something happenedā€™. Ex:

  {
    type: 'PLAYER_SELECTED',
    playerId: '3jdhdkjfd7d8f3',
    playerName: 'Nikola Tesla'
  }

These actions can be used for many purposes but it helps lay down a breadcrumb trail of things happening in your app. If other parts of your app need to know about a player being selected, theyā€™ll have all the info they need in one object.

Action creators are simple functions that create the object action above. They can do anything needed to construct this action before sending it off to somewhere else. This is where you can use a Meteor method. Since we have mini-mongo we can even do this synchronously! Ex:

function selectPlayer(playerId) {
  let player = Meteor.call('fetchPlayer', playerId); // could use Players.find too!
  let playerName = player.name || "N/A";

  return {
    type: 'SELECT_PLAYER',
    playerId: playerId,
    playerName: playerName
  };
}

Notice this is just a normal function that returns the action object from above. This little unit is also very easy to test. Below is an example of the UI setting data into the store using the above

// in the UI somewhere

store.dispatch( {
  type: 'PLAYER_SELECTED',
  playerId: '3jdhdkjfd7d8f3',
  playerName: 'Nikola Tesla'
})

// or we can use the action creator to keep the logic out of our UI

store.dispatch( selectPlayer(this._id) )  // this._id is blaze data context

Now the Redux reducer takes the dispatched action object and merges it into the single state. To keep it more brief the final store data look like this:

store.getState(); 
//returns:

{
  backgroundColor: 'blue',    // some other random part of the app
  selectedPlayerId: '3jdhdkjfd7d8f3',
  selectedPlayerName: 'Nikola Tesla',
  players: [{...}, {...}]   // you could store players here...or just use minimongo
}

Redux serves as an architecture to pass data around, and keep track of local state thatā€™s not persisted in Mongo. You could also store your players in this central app state if itā€™s easier to reason about.

I think Redux is 10x more simple than vanilla flux and iā€™m personally using it for all new project. Itā€™s such an improvement on flux. It builds upon the decades of functional programming concepts and the theory is time tested. If you look at advanced examples your eyes may glaze over but the above is what works for 90% of apps. This example is using Redux and Reactā€¦ itā€™s much simpler than Alt.


Thatā€™s a really valid point! If you donā€™t need it then I would go without it. Especially in small apps. Shipping good software is more important than using the latest tech :smiley: I also agree if you structure you app right itā€™s not needed. However then youā€™re using ā€˜some other app architectureā€™, which is perfectly fine of course!

At the end of the day most of the time is spent maintaining a feature, no creating the feature for the first time. Think about how much maintenance is required in an app like this forum! Having some kind of architecture helps this process go smoother

@SkinnyGeek1010 Iā€™ve heard a lot of buzz around Redux but havenā€™t yet checked it out. Iā€™ve got a few friends that rave about it! I think what I need to do is build an app and force myself to use reduxā€¦ That way Iā€™ll figure it out and will be able to tell when and if it would be useful (Iā€™m sure it is considering the hype around it). Iā€™m actually in the process of building an app with redux (after I read your post) so Iā€™ll let you know my thoughts after Iā€™m done with itā€¦ My wife is at a wedding all day so I have a lot of time to play with it :slight_smile:

Thanks for the links! They will be very helpful in getting things rolling. Also, thanks for the lengthy reply! I appreciate that you take the time to intelligently respond to questions/statements. Glad to have another React enthusiast around :+1:

Iā€™ll post here again when I get the redux app built!

1 Like

Nice let me know if you upload it! The guide has a basic todo app but with meteor itā€™s a bit less painful.

For me Redux compared to what I used to do (try and be neat) is like comparing React to jQueryā€¦ jQuery can get the job done but React is more organized and maintainable once you wrap your head around it.

@SkinnyGeek1010 alrightā€¦ lots of questions! haha. Here is the repo of the app I just made https://github.com/ryanswapp/meteor-redux-todos-react

I understand how Redux works now (at least I think haha). There is the all important store that manages all of your apps state. Your components dispatch action creators when things happen (e.g. form submission, clicks, etcā€¦) which return actions (objects that describe the changes to the current state) that a reducer reducesā€¦ This is where Iā€™m having trouble. What exactly am I supposed to put in a reducer? In this app, Iā€™m simply logging that a todo was either created or removed to the console. In a more advanced app, what would happen here? Also, I assume that the reason we are dispatching action creators every time something happens is because then we can look at the store somehow and see what has been happening in our app? Is that correct? If so, how do we see what has been happening in the store? I feel like I could keep asking questions all day long haha

Not sure how well this fits with meteorā€¦

Your actions are mutating server state, but your reducers are supposed to create new state. given a state => actions => reduced => new state => actions => reduce => next state

I think if you were going to do it meteorized youā€™d want to make your reducer sit on the server between all your ddp clients that see the same ā€œstate treeā€. in mongo youā€™d have a way of creating a state ā€œsnap shotā€ and a collection for ā€œactionsā€. So you can load a snapshot and start applying actions and reducing them.

actually, you should be able to recreate the state client side given a snap shotā€¦ then you just need to subscribe to a action collection and reduce any action that comes in and for creating actions, create them with a method call, insert into the action collectionā€¦

You got it! Youā€™re 90% there! Also for simplicity sake lets assume weā€™re using Redux for local state only, like for which tab is selected or temp form data, not for db data (weā€™ll get to that further down). For future readers iā€™m working off of commit afeaeb64d0 when writing this.

This is where Iā€™m having trouble. What exactly am I supposed to put in a reducer?

Yea so this is where it veers off the path of being easy to grok. To make things simple lets just keep it at one reducer. Ultimately the reducer is the place where you can look at an action and from that metadata, determine how to mutate the data. Using the switch we catch the action by itā€™s type. In a counter example we me check to see if itā€™s odd and if so then increment by 10, otherwise increment by 5.

So the next point, what to do with said dataā€¦? The first param is state. This is the single piece of state from your store (at this point). In your app the state is an array. Every time the reducer is called weā€™ll have access to it. If we pretend we donā€™t have a DB yet we can push to this array to build up our todos array. Eventually the UI can use this array to render.

Again for simplicity letā€™s pretend weā€™re not saving to the DB and this todo list only has to live in memory. Hereā€™s an example to add a todo to our state. Note I also had to change the action creator so that it passes the data in.

Actions.addTodo = function addTodo(text) {
  Meteor.call('todos/insert', text);
  return {
    type: 'ADD_TODO',
    text: text,
  };
};



Reducers.todos = function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      console.log("Added Todo!");
      // make a copy of old state
      var newState = _.clone(state);
      // push new todo into new state
      newState.push({text: action.text});
      // return newState which will become state
      return newState;

    case 'REMOVE_TODO':
      console.log("Removed Todo!");
      return state;
    default:
      return state;
  }
};

Now itā€™s adding to our state every time we use the UI (or dispatch from console) to add a todo. Check it out with store.getState() in the console. We now have an array that looks like this (after adding two):

[
  {
    "text": "Foo"
  },
  {
    "text": "Bar"
  }
]

Imagine how useful it would be when your entire app could look in the app state tree and see that "needsLargeFont": true would be. The component could render a more accessible version with a jumbo font and you donā€™t have to store that state in the component(s) itself.

So to recap, the reducer accepts the action from the dispatcher, if the type matches then it can mutate the state as needed. The reducer in turn will return the state object to the store where itā€¦ stores it :smile:

In a more advanced app, what would happen here?

Ok so imagine your reducer is hundreds of lines of codeā€¦ not so simple anymore :frowning: But thereā€™s a solution! You can use multiple reducers and combine them into a single { } root state object. For example say we want to also keep track of the form input text in case the browser hot-reloads. We can have two reducers that get passed to store and then getState will return the finished result like this:

{
  todos: [
    {_id: '1d2g3', text: 'Learn Redux'},
    {_id: '3h2g7', text: 'Take over the world'}
  ],
  forms: {
    create: "How now brown c",
    email: "sally@gmail.com"
  },
  musicPlayer: {
      foo: 1,
      bar: 2
  }
}
Reducers.forms = function todos(state = {}, action) {
  switch (action.type) {
    case 'ENTERED_TEXT':
      // ....
      newState.text = action.content
      return newState;
  }
};

Reducers.todos = function todos(state = [], action) {
 // ....
};

Notice that forms takes a default state of {}ā€¦ this is the object that is the forms key on the state object above. By splitting up reducers it allows you to think about just that part of the dataā€¦ kind of like making one button a react componentā€¦ youā€™re only concerned with one thing at a time (easier mental model). Each reducer is only concerned with managing itā€™s slice of the global state.

And we would combine them together like this, notice how it matches our state shape from above:

let rootReducer = combineReducers({
  todos: Reducers.todos,
  forms: Reducers.forms,
  musicPlayer: Reducers.musicPlayer,
});
store = createStore(rootReducer);

If so, how do we see what has been happening in the store?

The store also provides a getState func that you can use to retrieve it. Thereā€™s also a changed callback that you can use that fires whenever the state changes . This is why you have to return a ā€˜newā€™ copy of the stateā€¦ If you just pushed onto the state array it would be the same object and Redux wouldnā€™t know it changed (oldState === newState). There are also different bindings for UIā€™s react-redux is for React, I even made one for Blaze yesterday!.

So this is how you would use Redux for local state. To over simplify, if you wanted to use it for db data as well (I do to keep things simple), you can essentially call insert/update in the action creator. To get from Minnimongo into the store I have a collections action that gets fired any time the collection changes. When it does the action creator returns the new data with a find and then the reducer just replaces it (no merging/copying like above!).

I donā€™t have time to add that but would be happy to send a PR with annotations later. In the mean time checkout these for the implementation in the leaderboard app:

https://github.com/AdamBrodzinski/meteor-flux-leaderboard/blob/redux/client/reducers.jsx
https://github.com/AdamBrodzinski/meteor-flux-leaderboard/blob/redux/client/action_creators.jsx

@SkinnyGeek1010 Iā€™ve spent a good chunk of the day trying to figure this outā€¦ haha. I think Iā€™m almost there. Thanks for your explanations above! Super helpful :smile:

Iā€™ve been talking to some friends of mine that are non-meteor React devs and they use Redux on all their projects. Theyā€™ve been telling me that what I should be doing is using an action to load data into state and then using connect to merge the state with the props for my TodosList component. However, Iā€™m wondering if this is the way that it should be done with Meteorā€¦ It seems like youā€™d lose reactivity if you didnā€™t subscribe to publications in the getMeteorData() hook on a component, right? I know that getMeteorData forces a component update when the data changes so I guess I could set that up with an actionā€¦ What have you done to make it reactive?

Also, do you persist the state object somewhere? My buddy was talking about persisting state and I couldnā€™t tell if he was referring to persisting the actual state object in the database or simply persisting the data in the state object in their appropriate collection/table?

Lastly, the state object is stored in memory. Thus, if I refresh the page it is gone! You used the example of having a ā€œneedsLargeFontā€ property that you could look at to automatically bump the font. How are you persisting this information? (I guess this goes back to my last question).

Here is the updated version of my repo https://github.com/ryanswapp/meteor-redux-todos-react

So there are two options. You can sideload data with the mixin and use Redux for local state that is not persisted to the databaseā€¦ or you can remove the mixin and have Redux store the todos for you. I do the latter unless thereā€™s some kind of memory constraint.

You will lose reactivity at first, but thereā€™s a way to add it back in. With flux/redux the way to get data in to the system is to fire an action. You never directly mutate the state (actually it wonā€™t even let you). This means we need to watch the collection for any change and then fire a changed event. The action creator will just do a Todos.find() and add it to the payload. The reducer will take this and merge/replace the state. With the react bindings (connect) it will force a re-render.

    // trigger action when Players changes
    trackCollection(Players, (data) => {
      store.dispatch(Actions.playersChanged(data));
    });

Also, do you persist the state object somewhere? My buddy was talking about persisting state and I couldnā€™t tell if he was referring to persisting the actual state object in the database or simply persisting the data in the state object in their appropriate collection/table?

They were prob. referring to persisting local state in memory, not in the database. Iā€™ve stored the state in a Session before a hot-code-reload and then re-prime the store on reload. This is helpful for keeping form fields filled and such. If you do the above with the flux-helper package then the ā€˜Playersā€™ collection would be persisted with the normal Meteor ways.

Lastly, the state object is stored in memory. Thus, if I refresh the page it is gone! You used the example of having a ā€œneedsLargeFontā€ property that you could look at to automatically bump the font. How are you persisting this information? (I guess this goes back to my last question).

Yep so basically the same as the last paragraphā€¦ just watch for the on hot reload event and stash it in a Session, then on reload, prime it again. For the ā€œneedsLargerFontā€, itā€™s just a button that the user can toggle. In this case it was not meant to be saved to the DB but you could if needed.

Checkout this repo on the redux branch and run it and watch the console.logs and place the debugger in there to see it saving to the DB and re-rendering.

Hope this helps :+1:

There is nothing wrong watching ReactiveDict same way as Adam is doing with collections, so you can use that for things not related to minimongo and also get value from it to init store.

BTW, I am doing kinda offline countdown app, going to try React+Redux there.
Where should I put the the Timer itself, thinking about placing it into parent of the button which will do start/pause.
And calling it from onClick/Tap material-ui event handling function.
@SkinnyGeek1010 is it the right place? :smiley:

1 Like