-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Understanding MobX and when to use it. #199
Comments
I don't know Cycle and I have no interest learning it now, so I'll give my opinion on Redux vs MobX. I've been using Redux since it was released. It's very predictable and testable, but takes a lot time to write a full module, because it needs a lot boilerplate. Of course you can write helpers to help with less boilerplate, but it still needs boilerplate. I'm sure you can archieve the same predictability and testability with MobX if you implement it the right way. MobX doesn't force you to implement it in a specific way, you have freedom. You can even use the same structure as you would use with any Redux app: https://github.com/mobxjs/mobx-react-boilerplate/issues/8 I'm using MobX now because I can write code 3x faster than with Redux, but my codebase still is predictable and testable. In the end of the day Redux and MobX are just concepts. Choose Redux if you want to have full control over dispatching actions and transforming state. Go with MobX if you prefer to don't manually handle action dispatchments and state transformations. I think that implemented in the right way, MobX is a natural evolution of Redux, you can trust MobX to manage your state, you just tell what you want, instead teaching MobX how to do it. I think the main difference between Redux and MobX is that for Redux you need to "teach" how to dispatch actions and transform state, for MobX you just trust that MobX will do a good job, you just tell MobX "do it", and MobX does. (it's just my personal opinion after using both libraries/concepts in production) |
I've had basically exactly the same experience as you, and I think we've reached the same conclusion. Although I am curious, how do you test your MobX application? That's actually what caused me to write this in the first place. I was wondering how testing MobX was different from testing Redux. Redux is built on the reducers, has time travel and everything is explicit, which is what I think is the only true advantage it has over MobX. This is also it's disadvantage as well interestingly. So given all that how do you match the predictability of Redux with MobX in your testing? |
@AriaFallah I'm not an expert with testing, but just try to create your functions as pure as possible, for example: class MessageStore {
// bad
markMessageAsRead = message => {
if (message.status === 'new') {
fetch({
method: 'GET',
path: `/notification/read/${message.id}`
}).then(() => message.status = 'read')
}
}
// good
markMessageAsRead = message => {
if (message.status !== 'new') {
return Promise.reject('Message is not new')
}
// it's now easily mockable
return api.markMessageAsRead(message).then(() => {
// this is a pure function
// you can test it easily
return this.updateMessage(message, { status: ' read' })
})
}
} Redux is just javascript, just follow some of the concepts, for example, actions in Redux are the same as MobX, the only difference is the state transformation, for transforming state, create pure functions and pass data around. |
I've enjoyed the aromas emanating from the melting pot of ideas in JavaScript front-end development, but with such a smorgasbord it can be hard to decide what to eat. Redux is built on solid theoretical foundations with a simple essence and a growing community of experienced developers that makes it an extremely compelling candidate for many projects. That said, my foray into using it left me with a project that felt disjoint in its organization. In particular, I felt this way when coming back to my small project after an absence and needed to mentally trace the thread of logic for an asynchronous action through multiple functions and files. I fully admit that this experience probably reflects my deficiencies more than that of the tools, but nevertheless I was curious to explore a different balance which led me to MobX and my mobx-reactor experiment. In many ways, my experiment effectively replaces the suite of redux+redux-saga+immutablejs+reselect with MobX (and my library) in a way that is perhaps appropriate for some projects due to their size/scale/velocity/etc. What I learned in doing this is that I ultimately exchanged explicitness (i.e. verbosity/boilerplate) and disjointness (a positive in the context of testability and a negative in the context of organization) with tight organization and bit of implicit "magic" (via MobX managing updates through observables). One of the things I really appreciate about the Redux approach is the single stream of application events that occur in the form of actions dispatched through a single application store and processed by middleware which furnishes similar opportunities as available during request/response processing of traditional application servers. |
You write it like this is a quality that only Redux has. I have to disagree. There is no obstacle at getting 100% coverage for mobX powered app. In fact it is easier to achieve since the amount of code is smaller than with Redux. |
@capaj Ah okay that's good to know. Like I said above, I haven't done much testing with MobX. I assumed that Redux, where you have to write everything out explicitly, would be easier to test than MobX because there's less magic, but, as you point out, perhaps MobX is easier to test because that magic helps eliminate a lot of boilerplate that made sense anyways so you need to test fewer parts of your code. Regardless, the whole point of the post is to get perspectives like yours. I'm not trying to peddle everything in the main post as fact like one would in a medium post. I created it as a result of curiosity and confusion about the concepts of MobX and how it stacks up against the other more popular libraries. |
Some input:
Testing was not a problem (unlike RxJS parts!): I just have variables, I verify they have the right state on given conditions. That's all. Overall, experience with MobX is very good. |
Would be awesome if you share some of your testing approaches with MobX. |
@AriaFallah @hnordt Do you have concrete questions in regard to testing? |
@hnordt I have an article on mobx recipes in the making. Will include some samples and showcases on testing. Give me 1-3 weeks, I'll post it then. |
Well, as I wrote, testing wasn't much specific. At least in my use case. I added MobX in a very local way (controller level) in an existing AngularJS 1 application, to replace part of the code. |
@AriaFallah posted a short video to some info: https://www.youtube.com/watch?v=83v8cdvGfeA |
@Keats haha yeah, but I figured that since I was just summarizing the information here and not as much in depth I wouldn't promote it myself. I appreciate you posting it though 😄 . I have to thank @hnordt and @capaj for providing me with a lot of the insight I had in this thread to be able to make the video. |
IMO the most fundamental difference between Redux and MobX, from a conceptual POV, relates to the update logic. In Redux, a Reducer encapsulates all the possible ways in which a piece of state can be updated. i.e. you can't (directly) update that piece of state from outside. And the overall state/reducers is organized around this notion of update logic. In MobX, the state is managed inside observables, but observables act like free slots which accept data from the outside. So in order to tell how the state held by an observable is updated you need to look to all the actions that update that observable. Typically some (maybe all, depending on the case) of the update logic can be inside a domain class, making observables private and exposing only getters for observable values and the set of actions that update the private observables. But still the update logic will be spread across multiple actions. So depending on the case: either Redux or MobX will feel easier. I don't think it's related to the size of an application but more to how complex is the update logic. if a domain class can encapsulate all its update logic, and if the overall behavior of the class can be easy to reason about, then effectively MobX will feel easier than Redux (talking about the models not specific implementations). However if the update logic of the app is such that it can't be encapsulated inside the domain class (e.g. you can't call the class method directly from you UI callback, or the called action will lead to cascaded calls to other actions in other domain classes), then Redux model will feel more suitable here. |
I've never thought about it that way 😮 I do have one question though. When you say:
Could you elaborate on what you mean by |
Say for example, you dispatch an action Now with mobX, imagine you have 2 classes One may argue that |
I think this is the fundamental difference, most of the rest is just implementation details. Redux (and flux in general) forces you to write your data updates outside the components at the top of your application, and enumerate all possible updates. MobX doesn't enforce this, however there's nothing that prevents you from doing it this way. |
I see. So you're saying that you can't mutate two different domains, My question would be, couldn't you solve the problem by taking a "redux-like" approach and having a single store at the root that accepts these events? |
[Update] @AriaFallah this may answer your last question
So what could be the combination Reducer + Observable? IMO, the question has already been ansewred a long time ago by FRP. I'm not talking about RxJS here because Rx has only one half of FRP: the discrete part which is event streams. The other half is the continuous part known as Behaviors (cf. original paper of Conal elliott on FRP) A Behavior models also a time varying value. But unlike discrete streams, a behavior has always a value (even from the start). here you can view it like an observable but which can not be updated arbitrarily. When you declare a behavior you must declare its update logic at the declaration. And the update logic can be specified with 2 things: the Event streams which affects the behavior state and the reducer which will handle the event streams) Here is a simple example of the todos example (I've made this example from a rough sketch but I think the concept could benefit from being implemented in a well tested lib like MobX) // toggle$, toggleAll$, ... are event streams
function newTodo(id, title) {
return {
id, title,
done: behavior(
false, // start value
[ toggle$.filter(eqF(id)) , done => !done ], // reducer for toggle events
[ toggleAll$ , (_, done) => done ] // reducer for toggleAll events
)
}
}
export const todoList = behavior(
[], // initial state
[
addTodo$,
(todos, id) => todos.concat(newTodo(id, editingTodo.value))
],
[
removeTodo$,
(todos, id) => todos.filter(t => t.id !== id)
]
)
// computed property
const allDone = computed(() => todoList().length && todoList().every(t => t.done())) Like in Redux, you can trigger an update from an event stream and it'll update all the behaviors depending on that event |
And of course the other way is also possible. You can embed behaviors in Redux (with some restrictions thou to make serializability/hot reload possible) and implement an automatic dep. model (like observable selectors) on top of that |
I actually was looking into something similar recently while looking into FRP. I was experimenting with combining MobX and Most.js to get a mix of the event and behavior/cell streams, but didn't get very far. I guess my question at this point is that if FRP with both event and behavior streams is the solution, how come it hasn't been created/used yet? Are there any drawbacks? |
Cant say this is THE solution, this is just my POV. Many will find no issues on writing code with free observables b/c it maps directly to their mental model. Others will prefer FRP style updates. And domain space can also make either option more appealing That being said, and although I didnt looked much into different libs I think Bacon.js has a similar concept called But AFAIK there is no lib which combines the dynamic dep. model of Mobx/Ko with FRP reactive behaviors. For example, in Bacon you cant access to the value of a property directly using |
And I'd just like to add that, in my POV, there is an added value on putting as much as you can of your logic into the 'pure side'. The world of functions is 'eternal': a relation between 2 things is like an invariant captured in your program, insensible to time, and wont be affected by how things get sequenced on the external world (i mean the relation). You can view it like eager (non lazy) mobx derivations which ensure consistency w relation to the event world |
Forgive me if I'm asking too many questions, but I have a few more.
|
Don't want to interfere to much in this thread, because it is way more interesting to discover how people perceive MobX than having me talking about how I intended MobX ;-). But the cool thing that @yelouafi is onto here is probably that the behavior objects are observable, trackable and seemingly mutable to the outside world. However to mutate an object, you have to invoke one if its actions which is still a pure function, thereby easily testable, snapshottable and all advantages that come from that. So it moves the whole tracking / observable thing closer to the FP ánd event streams world, but without giving up on referential consistency etc yet (or gaining the general advantages of immutables) (if I see it correctly). I think indeed this pattern could be built on top of MobX quite easily. Especially with the |
@AriaFallah simply put you can write a program in terms of relation input-output and have the underlying runtime ensure the relation always hold. Actually mobx ensures ref. transparency between obs. and derivations but the part the goes from the event to the obs. mutation is outside of its scope. I dont emphasize on immutability at this level. In fact the whole purpose of immutabiliy in FP languages is that you cant have ref. Transparency with mutable values. At the 'FRP level' if I can ensure my relations are maintained (ex an observable array propagates change while the underlying raw array us mutated) I'll be fine with it. My goal is not immutability but ref. Transparency. @mweststrate that would be interesting. If we can ensure trandactional semantics for behavior updates (a root event updates the state in a single transaction like Redux have actually) then I think mobx would make a great complement to actual discrete event stream libs |
see transaction? Op di 24 mei 2016 00:37 schreef Yassine Elouafi notifications@github.com:
|
@yelouafi didn't test it, but an implementation of import {observable, action, asReference} from "mobx"
function behavior(initialState, ...actions) {
// store state in an observable
// we use asReference for a one-size-fits-all approach; treat any value as a single atom
// not using asReference would be slightly efficienter for tracking (e.g. individual properties can be tracked)
// but would need to deviate per type (e.g. use array.replace, map.merge or extendObservable to merge a new state into the current one)
const state = observable(asReference(initialState))
// make sure only our reducers can modify state
let isRunningReducer = false
state.intercept(change => {
if (isRunningReducer)
return change // OK
else
throw new Error("State should be modified by emitting an event")
})
state.observe((newValue, oldValue) => {
// do cool stuff for time travelling, debug tools or similar
})
// subscribe to the streams, actions guarantee transaction semantics
actions.forEach(
([stream, reducer]) =>
stream.subscribe(action(event => {
isRunningReducer = true
state.set(reducer(state.get(), event))
isRunningReducer = false
}))
)
return () => state.get()
} |
I've been thinking about this a lot too. I really like the way code is written in Mobx but I prefer the Redux code organisation and pattern (Flux). A while ago I had an idea to build a redux-like API on top of Mobx. I stopped because first I want to build a big app using redux/redux-saga and another one with mobx, which is what I am doing that right now with two of my projects. Anyway, I'd love to share my thoughts with you in case they are of any interest. I've been thinking a lot about how the API would look like more than the implementation details, but I think it would be easily built with Mobx v2. I don't want to hijack this issue so for anyone interested I've created a gist with my latest thoughts. Feel free to let me know what you think: My plan is to keep improving and simplifying the API while I gain more and more experience with both Redux and Mobx, always looking for better easy-to-write, easy-to-reason, easy-to-test and no-boilerplate patterns. |
@mweststrate can you explain how observable works internally and is state immutable in mobx? |
Mobx state is not immutable. Here's a video that goes over how mobx works On Oct 17, 2016 10:47 PM, "Kamaraju prathi" notifications@github.com
|
@mattruby Thanks |
@mattruby It would amazing if you could set some type of configuration to tell MobX to work as immutable. Except not immutableJS cause.. ain't nobody got time for that. |
Check out mobx-state-tree. On Oct 31, 2016 11:06 AM, "Sterling Cobb" notifications@github.com wrote:
|
EDIT: Will rearticulate this later once less drunk. |
@joelday regarding that last comment; MobX already has Knockout style computed observables? Or am I missing something here? |
The momentum around mobx reminds me of when angular's two way data binding first came out. It really was a joy to use compared to the frameworks of the time. However the honeymoon eventually ended when large real world projects gave birth to the state soup problem. Devolper velocity further decreased whenever you had to open the angular internals, namely the compiler. I'm wondering out loud if developers are still in the honeymoon stage with mobx. Namely, what sorts of problems are encountered with mobx in large, complex apps? Seems too early to tell. A second issue alluded to earlier is the state soup problem. One way data flow solves it. As far as I can tell, mobx brings it back. There's nothing to keep you from creating an app of interconnected objects with cyclic dependencies, right? |
@frankandrobot while you can do two-way data binding in MobX, you are not advised to do so. In fact, when using strict mode, all state mutations need to happen in the context of an action which means you can get a clean trace of everything that happened in the program, and why it happened, a bit like Redux. It is just that redux asks (but doesn't enforce) you to always return a new object while MobX lets you modify the state and keeps track of it. That being said, it is left up to the developer to decide on the best place to put their actions. You can put them all in a single store/service, you can put them in different stores/services based on domain, or you can indeed sprinkle them everywhere and end up with a mess. Redux in this regard is more beginner-friendly (some would say noob-proof) in that it stipulates that all the state mutations always happen in the same place (reducer). |
Have you tried writing a big SPA with MobX? I have and I certainly did not enter state soup problem. I keep my state minimal, derive anything that can be derived. Everyting is fast, smooth, readable and concise. |
@frankandrobot While mobx does not actively prevent the user from two way data-binding without |
@capaj Any more details about your team? If you're telling me it's a one person team, of course, mobx works! Any framework works when it's only one person. Interested in seeing how the architecture holds up on a good size team (+4) on a large, complex app and over the long term. I've used redux before in this scenario and the architecture held up pretty well. @marvinhagemeister interested to know what your thoughts will be a year from now :-) |
We are working with ~18 people on a MobX based project for almost 1.5 year now at Mendix. Would not be surprised if Wix, Lyft or Microsoft teams are working with similar sized projects. Don't know much details about those however, as these are internal projects and I am not involved in them. If you search the reactjs reddit you'll find quite some testimonials from big development teams using MobX Edit: forgot not in a critical place :) |
@frankandrobot Our current team for this particular project is around ~8-10 people (some part time freelancers). Perhaps the difference is that the redux community has settled on a few preferred ways of doing things (reselect, redux-saga,...) compared to MobX. Nonetheless both redux and MobX are excellent for state-management. One can't go wrong with either. I'm curious to what the future holds as well, especially when |
Both Redux and Mobx are awesome state managers, but if I would have to bet for the future state manager that'd be mobx-state-tree. It has the best of both worlds (and even more). |
@luisherranz That looks very interesting. It's more like an "extension" for mobx though and not a replacement. In our case we've gone with a similar route. Our state has a tree shape and each model has a de-/serialize method which has been great when loading data from an api and constructing models. I'm really excited about the snapshot feature of mobx-state-tree though! That's the only thing I miss with mobx coming from redux. |
Pretty easy to add snapshots using just MobX's createTransformer. Each time
a model gets serialized it will return the same object if nothing changed.
So you can just run that in autorun and save the snapshots somewhere to
reuse later.
…On Jan 31, 2017 21:44, "Marvin Hagemeister" ***@***.***> wrote:
@luisherranz <https://github.com/luisherranz> That looks very
interesting. It's more like an "extension" for mobx though and not a
replacement. In our case we've gone with a similar route. Our state has a
tree shape and each model has a de-/serialize method which has been great
when loading data from an api and constructing models. I'm really excited
about the snapshot feature of mobx-state-tree
<https://github.com/mobxjs/mobx-state-tree> though! That's the only thing
I miss with mobx coming from redux.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#199 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/ADlcCmdrVK1R-H78NDWmAdEKnkpXsWfmks5rX51KgaJpZM4IKFcX>
.
|
Hi, we are building an app for displaying various data we getter from different IOT devices (sensors, cameras,.. ). Frontend has really minimal logic, basically it just displays data into various sections. And user can create some filters for data. The app is currently in redux but it seems like an overkill and there are some big issues with unecessary rerenders which would require lots of effort to fix. So we want to rewrite it and are deciding between, mobx or immutablejs + some cursor library. The logic in app is really simple. Is mobx a good match for such application, how does it perform when merging large jsons into state. |
Yep, should be no problem :)
Op do 9 mrt. 2017 om 21:55 schreef Marek Sedlacek <notifications@github.com
…:
Hi, we are building an app for displaying various data we getter from
different IOT devices (sensors, cameras,.. ). Frontend has really minimal
logic, basically it just displays data into various sections. And user can
create some filters for data. The app is currently in redux but it seems
like an overkill and there are some big issues with unecessary rerenders
which would require lots of effort to fix. So we want to rewrite it and are
deciding between, mobx or immutablejs + some cursor library.
The logic in app is really simple.
apply filter -> fetch data -> merge into state -> react render.
Is mobx a good match for such application?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#199 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/ABvGhGgqGgZErRLi7jnFxqWrIq1u5mYGks5rkGc4gaJpZM4IKFcX>
.
|
You can use cycle.js in place of redux/mobx for a React app? I never thought to do this, is there anywhere I can read more about this? |
@GitCash send 0.02 BCH to @mweststrate Really sorry for the hideous large banner below. This is first time I'm trying the new GitCash tipping bot, and I wanted to tip my favorite project, but obviously hadn't thought this through. Now this just looks like a bad ad for Bitcoin Cash. :-( Update: The GitCash bot was banned from GitHub, so the message below has been removed. I also realized that everybody subscribing to this thread got the image in an email. Double bummer. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Recently having used MobX, I'm trying to reason about why I'm really using it, and trying to truly understand the pros/cons vs redux and cycle.
For redux, I think that it comes down to the fact that most people do not need the number one thing redux has to offer, extreme predicability and extreme testability, because their apps are not complex enough. Thus, when they're writing a bunch of reducers, dealing with extra verbosity, and having trouble grasping the new concepts, only to not reap the benefits, they feel like redux isn't all that useful.
For cycle, I feel like the same way you've written in your docs, most people don't need the complexity and power that RxJS brings to the table over the more simple API MobX provides. MobX also lets you stick to the OOP style that most people are familiar with unlike Cycle, which heavily favors pure composable functions.
Basically MobX lets you write your code as you normally would without forcing you to adopt and learn many new paradigms, and moreover, abstracts away the need to understand your view rendering logic. I think the real power of MobX is the fact that it's just easy.
However, this also makes me wonder.
Redux, ignoring its other limitations and its verbosity, will allow you to write an application that you are familiar with from top to bottom. If you put in the work, it'll be easy to get 100% coverage in tests, and to reason about piece by piece how your program flows.
Cycle despite being more complex, following different paradigms, and needing you to understand RxJS ultimately seems more powerful than MobX if you grasp everything about it.
Do you think the above is accurate?
Also, where do you think MobX fits in when your application grows to be very complex? Like I said above MobX's appeal to me is that it provides similar results to the more complex libraries all while keeping it simple and easy to learn. But when should one pick MobX over Redux or Cycle when they're fully committed to learning and accommodating the complexities of either library? While MobX seems just as capable, the alternatives seem more advantageous if you invest the large amount time necessary to understand them. Is this accurate as well?
An obligatory thank you for writing the library. I'm using it, and enjoying using it. This isn't a critique or anything, but just a deeper dive into understanding its place among other available tools.
The text was updated successfully, but these errors were encountered: