I am wondering weather I should learn and use it.
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 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
Asking questions (to learn, not to challenge!)
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.
Thanks this helps a lot!
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
So this looks just like another syntax for Meteor methods. Whatās the difference?
Great glad it helped
[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
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.
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.
@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
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 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
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
Iāll post here again when I get the redux app built!
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
In a more advanced app, what would happen here?
Ok so imagine your reducer is hundreds of lines of codeā¦ not so simple anymore 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
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
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?