Is there really any need to use Flux in Meteor?


#1

@SkinnyGeek1010

I was looking at the flux chat demo here

and I was thinking why do we even need flux in meteor? I understand React is necessary but why Flux? Take a look at this package, it implements everything they achieved as well.

https://atmospherejs.com/socialize/messaging

By the way, please don’t go bashing me as a flux hater or something. I am carefully examining the situation to understand whether there is any sizeable benefit of using Flux architecture.


Wait, what's the point of Redux again?
A declarative vision for Meteor and React apps & flux hype?
#2

I’ve also been questioning this lately. I’m in the process of building a rather large and complex app. I started of using Redux based on @SkinnyGeek1010’s example. However, as I progressed I didn’t really see the benefit of using Redux. Minimongo basically acts as a store, and using the ReactMeteorData mixin takes care of keeping the React components up to date (I still use a smart and dumb component approach, but instead of using connect (from React Redux), I use the ReatMeteorData mixin). Some stuff like UI state doesn’t go into collections, so for that I create a reactive-dict.

When I was using Redux, I ended up spending a lot of time setting up reducers and actions to update those reducers when a collection changed. I frequently had issues with the reducers not having the same state of data as the collections, either because of bugs or because I forgot something. And in the end, I didn’t exactly benefit from using reducers vs. the collections directly.

I still create actions though, but instead of being called through a dispatcher, they are simple functions which I call from my components (basically just to add a level of abstraction between my components and models).


#3

If you don’t use flux, you’re still going to end up constructing your app with flux concepts. Stuff still will flow downhill. You still will centralize state, whether in mongo or the top level component.

@SkinnyGeek1010 likes Redux. The guys on the meteor club react podcast didn’t see the need for flux.

In the end I think it’s a function of personal preference and perhaps the complexity of your app.


My headache with React - JQuery Plugins and Porting TF theme to Meteor
#4

Yeah, I guess in the end I’m still pretty much using flux, just not any specific flux framework/library.


#5

I think this is a really great point. The more complex your app is and/or the more you’re working on a team the more attractive a ‘standard way’ of structuring data the more attractive Redux is.

Basically if you don’t have issues sticking ‘local state’ (non mini-mongo data) into Session/Dicts than I would just roll with whatever works best. My personal experience is that it’s very fast to get started this way, but this velocity decays over time.

My other app using Blaze and scattered Session data was very very fast to work in on day 7 but much much slower to iterate on a year later (x10 for debugging).

Redux really makes it easy to know where to look for data/bugs in larger apps, and also keeps it in one spot. Mostly this is just because of convention, and organization, not some kind of secret Redux sauce.


[quote="coniel, post:2, topic:12957"] I frequently had issues with the reducers not having the same state of data as the collections, either because of bugs or because I forgot something. [/quote]

Yea using the mixin to use minimongo data is by far the easiest and most ‘Meteor’ way. The main reason I am using Redux for collection data is so that the data layer is decoupled from Meteor… that way anything could trigger an action and the UI gets data… i’m more less hedging my bets. Some limited tests had Redux being more efficient but I think that’s not an issue for most pieces of UI (React is pretty fast!)

However, I still find Redux easier than using Session/Dicts… perhaps this is from using it ReactNative so it’s easier for me at this point.


#6

The short answer is you don’t need to use it but it’s helpful for larger apps or working with more people.

So I think it’s easiest to understand if we think about it without the database and without Meteor and then add those in as the example goes on.

For example say you have a todo list app and your customer wants the header to turn red if there are no todo items and to turn white if there are todo items.

If we’re using vanilla React this becomes an issue because the Todos component is storing the data in it’s this.state variable (in memory). There is no way for the header to reach in and check how many todos there are and the Todos can’t talk to the Header component (by design).

So the solution to this is to move the todos data (list of objs) from the Todos comp up higher to the App component. In this example App is root component. We’ll stick this in App’s state and call the key todosData.

Now Todos and Header are both a child of App so we can pass the todos data down as props to both of them using
<Header todosData={this.state.todosData} />
and the same for Todos.

Great so now we’re passing data down to each component and they can declaritively do their thing according to their rules (making the header red if props.todosData.length is 0.

However now we have an issue as our app grows. Once you have many of these things to push up to the App level it starts to get confusing. If you’re working on a component that is 5 layers deep, you’ll have to walk up the tree to see where this trail of ‘props’ stops. Also if you move it you’ll need to re-do the ‘pushing it down as props’ dance.

Also if you have 10 layers deep of components, perhaps you go up 3 layers and add your data there. This makes it easier to ‘thread data through props’ but now your data is in two places… App and this other component sandwiched in between. Clearly this isn’t going to scale (as in code size). I’ve ran into this in a small-ish React Native app.

Ok so now we drop this into Meteor (with no db yet) and instead of keeping that local data in App we’ll use the Reactive-Dict to store the data in lib/my_state.js and we’ll expose it globally as dstate to keep things more clear.

We can now buildup a todos array and set it back into the dict so that any other UI can update itself when the data changes via the mixin. When the click or form handler fires we can just set the data and call it a day.

However if we want to make maintenance and testing easier we can put our insert logic into a separate function… lets call it addTodo and we’ll put it in an actions.js file so that it’s out of the way. Now in the function it’s taking a para of data and it’s just fetching the current postsData, pushing the new post (from the param) on the array and then saving it back using dstate.set('postsData', newPosts);.

Now the click/submit handler gets the form data and calls postsData(formStuff) and calls it a day. It’s just responsible for listening to the submit event and forwarding it on. Keeping the views dumb makes it easy to test.

Cool now it’s also super easy to test the logic that inserts a post because it’s just a function!

Ok so how cool would it be if we could log all the things that change state (in dev) so that when we’re debugging we can watch the trail of changing data and see why our app isn’t acting the way we want. We could add a console.log into every one of those functions in the actions.js file but with tens or hundreds that seems tedious… unless we do it automatically!

Right, so to do that all we need to do is wrap our reactive-dict with a function that will log any of the params that we pass in, then it passes it to the dict… kind of like tapping into the middle of this ‘flow’:

var _dstate = new ReactiveDict('appState');

dstate = {
  set: function(key, val) {
     console.log("State", key, val);
     _dstate.set(key, val)
  }
  get: ...
}

Right. now it has the same API as before but we get logging. (this is the same concept as Redux middleware)

So now we’ve pretty much re-implemented a basic version of Redux. It doesn’t have reducers but we’re basically handling that in the action file. In a larger Meteor app you could make your own reducer layer and just make the action functions return an object with a type like ‘ADD_TODO’ and that second layer would then handle the data changing.

Using a pattern like this could be much simpler than using Redux for smaller apps. For larger apps the learning curve and boilerplate would likely be worth it (as well as for faster onboarding with teams). However since we have Reacive data and the mixin, we don’t need the event driven system flux/redux has… we can use the mixin to detect changes.

Hope this helps!


Redux for Blaze Example App
#7
  1. If all you’re using is minimongo, forget the flux pattern.

  2. If you have client-only state (e.g. stuff u usually store in Session storage or Template.instance().key = new ReactiveVar that only trickles down from parent to child template and so on in one branch, use Template.instance().key and pass it down via helpers to child templates. And don’t use Session storage; never use that anymore.

  3. However, if you have 2 “leaf templates” that share client-only state (NOT FROM MINI MONGO), i.e. to store the value entered in a search field, abstract that state out to another object that will maintain the state.

a) In a React app it will need to notify listening components/templates when that state changes, and Alt or Redux is a great option. Since you have the benefit of Minimongo for everything else, you shouldn’t have to worry about unnecessarily complicating your app until you absolutely need to. That said, you don’t actually need both Actions and Stores at this point, just one object that handles both–perhaps Redux is a better solution than Alt at this stage since it’s simpler, being focused around one Reducer.

b) In a Blaze app template helpers can just initially get the state in a method off that object. That object in the Blaze version must of course store that state on itself with ReactiveVar, ReactiveDict, ReactiveField or ReactiveState–the latter of which is the newest and most feature packed: https://atmospherejs.com/meteorflux/reactive-state . That in itself could be the object I’m talking about. However, this isn’t the full Flux pattern, but is the perfect step until the complexity of your app necessitates the hopefully slightly lesser complexity of more decoupling.

  1. The level of complexity that would require the full Flux pattern is: if some reason you now have 2 of those ReactiveState objects (e.g. because they are catering to separate groups of leaf components) AND more importantly if one of your events trigger state changes in both state stores, then you need to add the second decoupling of the Flux pattern: creating the many-to-many relationship between Stores and Actions. In React, now use Alt, Redux, etc. In Blaze, you have to make your own reactive event dispatcher–it doesn’t need to be very complicated; it basically has its own simple binary state stored, say, in ReactiveDict, and in an autorun dispatches a notification to a Store (or ReactiveState) when its a key in its ReactiveDict changes back and forth from true to false or something like this. Your events in your templates call a method on that Action object that trigger the true/false change and pass along any parameters, which will eventually be passed to stores listening to the actions, which in turn will pass the state changes designated by those parameters to listening templates/components (who are listening through tracker style reactivity).

Now, as far as immutability, I’m not exactly sure what to tell you at this point in time. Again we are talking about the Blaze version. The React version you’re using Alt or Redux by this point in time. @SkinnyGeek1010 may have a better idea? What do you think? We are using state in the Action to dispatch events because that’s how Tracker reactivity works–perhaps the action doesn’t need that; I was just keeping it consistent with the tracker style. The action could just have the mappings to stores and manually dispatch to the stores, rather than stores initially connecting to the Action through a “pseudo subscription” via get() in an autorun of its own (similar to how a helper listens to it). Regardless the stores will have to maintain state reactively so tracker will notify template helpers. And no matter what if you’re using one of the ReactiveX packages, immutability has gone out the window and you’re dealing with state by reference. I’m curious about the dangers that leaves us with if any??


#8

I would say the easiest way to go is to just not mutate by convention. For example if you’re using Blaze and a ReactiveDict you can use ES6 spread syntax to essentially copy the data:

const oldData = dstate.get('playerInfo') 
// >> {selectedId: '123', selectedName: 'Bob'}

const newData = {...oldData, selectedName: 'Bobby'}

dstate.set('playerInfo', newData);

#9

Yea I think this is something that I forget to mention a lot too. If your app is basically a crud app that doesn’t keep a ton of client-only state than it’s not too big of a deal.

My recent app had a huge 10 page form with several hundred inputs and the user could go back/forward between pages and these inputs would have to validate themselves and finally the form container would have to validate the entire form and leave a visual checkmark for each page/step or X for incomplete, so there was quite a bit of state to handle before ever submitting the data to minimongo.

Redux is a lifesaver in this case (the time traveling debugger is useful here too).


#10

I was looking on cycle.js presentation yesterday 8:06:00 https://www.youtube.com/watch?v=9cIEtC-V2XE
That Observable.js seems kinda similar how we are tracking changes in Meteor.
There were many more interesting presentations on Reactive 2015.
And the presentation before that Cycle.js one was by guy who mentioned Meteor few times during it :smiley:


#11

This is super clear & helpful! Do you write more of this? (blogs, articles or something?)

Thanks for writing this!


#12

Thanks! I have a Medium account but don’t write much: https://medium.com/@skinnygeek1010
Though I do want to write an Elixir for beginners book… not enough time though!