mobxjs / mobx

Simple, scalable state management.
http://mobx.js.org
MIT License
27.49k stars 1.77k forks source link

Understanding MobX and when to use it. #199

Closed AriaFallah closed 5 years ago

AriaFallah commented 8 years ago

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.

hnordt commented 8 years ago

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)

AriaFallah commented 8 years ago

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?

hnordt commented 8 years ago

@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.

amsb commented 8 years ago

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.

capaj commented 8 years ago

@AriaFallah

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.

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.

AriaFallah commented 8 years ago

@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.

PhiLhoSoft commented 8 years ago

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. OK, it changes some things: instead of doing ctrl.observedVar = true for a quick test, I have to set up the variables on which observedVar depends to do the test. Logical. Also, Jasmine is a bit lost with special MobX objects: its isEqual works well with them, but if the values differ, it reports something like "got { name: (getter) } instead of { name: "foo" }", needing a bit more work to see what went wrong.

Overall, experience with MobX is very good.

hnordt commented 8 years ago

@capaj @PhiLhoSoft

Would be awesome if you share some of your testing approaches with MobX.

hellectronic commented 8 years ago

@AriaFallah @hnordt Do you have concrete questions in regard to testing?

capaj commented 8 years ago

@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.

PhiLhoSoft commented 8 years ago

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. Not using it as a central store, as it would have been too disruptive. We have already some unit tests (made with Jasmine, run with Karma, classical in the Angular world), not enough because of deadlines and so. But well, the updates I had to do to accommodate the introduction of MobX were minor, as explained above. That's the beauty of MobX: it is quite transparent... And not opinionated, so we can use it outside of React, in a non pervasive way, my way and not in the way envisioned by the project.

Keats commented 8 years ago

@AriaFallah posted a short video to some info: https://www.youtube.com/watch?v=83v8cdvGfeA

AriaFallah commented 8 years ago

@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.

yelouafi commented 8 years ago

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.

AriaFallah commented 8 years ago

@yelouafi

I've never thought about it that way 😮

I do have one question though. When you say:

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.

Could you elaborate on what you mean by can't call the class method directly from your UI callback, and also why it's easier to use Redux in this case vs MobX? I'm having trouble visualizing what you're saying. Is it that MobX can't wrap all of the update logic in a single place like Redux reducers can?

yelouafi commented 8 years ago

Is it that MobX can't wrap all of the update logic in a single place like Redux reducers can?

Say for example, you dispatch an action TODO_ADDED and you have 2 reducers : todoList (to add the new todo to some list) )and onBoarding (for ex. to track user progress) which both react to the same action. In the Todo UI, you'll only do dispatch(todoAdded(...)), the store will dispatch the action to all reducers and both reducers will perform the update logic internally.

Now with mobX, imagine you have 2 classes TodoList and OnBoarding which also both watch for todo creations but each has a different update logic. In this case, you'll have to trigger 2 actions from the UI, one for each class, but then you'll have a part of your update logic in the UI. You may also embed the call to OnBoarding inside the TodoList.addTodo(...) method. Or make a supervisor class which encapsulates the whole logic, and then call the supervisor class from the UI but then you'll have those cascaded actions (e.g. you aren't calling OnBoarding.todoAdded(...) directly from the UI)

One may argue that OnBoarding could be made a reaction to TodoList, but what I'm emphasizing here is that we're not interested in reacting to state changes but to the event itself (e.g. we may want to watch for 3 consecutive TODO_ADDED events)

glenjamin commented 8 years ago

Is it that MobX can't wrap all of the update logic in a single place like Redux reducers can?

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.

AriaFallah commented 8 years ago

@yelouafi

I see. So you're saying that you can't mutate two different domains, Todos and OnBoarding for example, at the same time without using something like computed, which isn't reacting to the event itself, but to the mutation of another observable.

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?

yelouafi commented 8 years ago

[Update] @AriaFallah this may answer your last question I want to add that the 2 approaches are not mutually exclusive: we can have advantage from the 2 words by combining the pros of each approach

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

yelouafi commented 8 years ago

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

AriaFallah commented 8 years ago

@yelouafi

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?

yelouafi commented 8 years ago

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 Property and also flyd streams may take an initial value.

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 prop() but you'll have to snapshot it with some event stream.

yelouafi commented 8 years ago

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

AriaFallah commented 8 years ago

Forgive me if I'm asking too many questions, but I have a few more.

There is an added value on putting as much as you can of your logic into the 'pure side'

  • When you mention purity, at least in relation to MobX, I think of its emphasis on mutation. Do you think there are any benefits MobX can gain from being more pure/immutable? When I think of immutability, I think of thread safety, which doesn't apply to JS, referential integrity, which MobX already gets through observability, and shallow comparing, which I don't think is all that great. Is there something I'm not thinking about...maybe in a big picture sense?

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.

  • I feel like this answers my question above in a sense, but I don't really have a concrete understanding of what you're trying to convey. I get that you're saying because pure functions are deterministic and referentially transparent, they capture an invariant relationship between their parameters that doesn't depend on anything except their values, but how does that relate to mobx derivations and the big picture in general?
mweststrate commented 8 years ago

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 intercept api in 2.2 with which you can control where and when objects can be mutated and make them immutable for the outside world.

yelouafi commented 8 years ago

@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

mweststrate commented 8 years ago

see transaction?

Op di 24 mei 2016 00:37 schreef Yassine Elouafi notifications@github.com:

@AriaFallah https://github.com/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 https://github.com/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

— You are receiving this because you were mentioned.

Reply to this email directly or view it on GitHub https://github.com/mobxjs/mobx/issues/199#issuecomment-221116846

mweststrate commented 8 years ago

@yelouafi didn't test it, but an implementation of behavior with mobx@2.2.0-beta.1 would look like this I think:

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()
}
luisherranz commented 8 years ago

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: https://gist.github.com/luisherranz/afc77fe8e74e06dd0ed666a118d5b0ce

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.

AriaFallah commented 8 years ago

@luisherranz

One problem with what you're presenting is that redux-saga only has so many advanced features because it's using generators. If you switch to async-await, you'll lose most of what the current saga implementation provides because async-await only deals with promises so it won't know what to do with .call() as just a single example as that doesn't create a promise.

luisherranz commented 8 years ago

@AriaFallah thanks. That's not a problem as long as you are in the middle (saga.call can return a resolved promise if the function passed is synchronous) but maybe we should use the gist comments for that type of things and keep this issue only for when to use Mobx in contrast to Redux or possible implementations on top of Mobx and what improvements that may have.

xgrommx commented 8 years ago

@mweststrate Your behavior and behavior in cfrp from @yelouafi pretty similar to join pattern in BaconJS http://baconjs.github.io/api.html#join-patterns but with update method instead of just when Also you can take a look in https://github.com/yelouafi/cfrp/issues/1

mweststrate commented 8 years ago

@xgrommx no join patterns are fundamentally different from transparently tracking deps. Because the first require you to declare, provide and combine the required dependencies, while the latter determine at runtime which observables are used and hence should be tracked.

It is like comparing JQuery with React. With both you can build a DOM, but with the first you specify which DOM nodes should be created, updated, replaced etc, with the latter you specify what the DOM should look like and it determines for you what DOM nodes should be created, updated or replaced to achieve that. Which significantly reduces boilerplate and cognitive load. See also: https://github.com/mobxjs/mobx/wiki/Mobx-vs-Reactive-Stream-Libraries-(RxJS,-Bacon,-etc)

yelouafi commented 8 years ago

@mweststrate Thank, I'll give that a try.

One concern I have is with handling transaction from 'simultaneous events'. MobX is glitch free with regard to its internal dependency graph. But when connecting the behaviors to external event streams we may end up with situation when some root event (e.g. button click) cause 2 notifications (e.g. deselect a shape and select another shape). From the FRP POV those events are occurring simultaneously and need to be handled in the same transaction. However with the above example (If I m not mistaken) the 2 simultaneous events will trigger 2 consecutive transactions.

thomas-jeepe commented 8 years ago

@yelouafi

If you just do

'''javascript

transaction(() => { this.boxA.selected = false this.boxB.selected = true })

'''

Mobx will consider both changes "simultaneous" and call all dependcies (boxA's and boxB's) at the same time.

If it isn't in transaction, then boxA's dependencies will be called first then boxB's.

monfera commented 8 years ago

tl;dr it may not be just a style issue; FRP inspired libraries are a good match for interactive models, data visualization and games (ideally 60FPS applications) and I feel these are hard, convoluted or verbose to do efficiently with Redux; glad to be corrected.

@AriaFallah I've used various FRP inspired approaches and one of their benefits in general (incl. MobX, most.js, xstream, flyd, kefir, bacon, RxJS in particular) is that they allow you to construct a directed acyclic graph of data dependencies, where the result of each node is only dependent on the inputs it gets, with the possible fold of its own history (e.g. scan).

I usually work on analytics and/or data visualization, and these areas tend to have fairly deep data flow graphs. For a simple example, think of an event stream (e.g. stock prices via websockets) that gets aggregated into histogram bins and the histogram is plotted with SVG. Things like sample count will determine how many bins to even have; each incoming event falls into a bin; and for the plot, there's calculation of axis scale, axis tick resolution and possibly, outlier bounds, plotting standard deviations or confidence intervals, and plotting the bars and maybe individual points. Then the user can zoom or pan which impact the view, or the user can slide parameters such as day lags for a Moving Average Crossover financial indicator.

I know how to construct such deep dataflow graphs with FRP inspired tools, and one benefit is that you can smartly and relatively easily cache values, or apply on-line methods (e.g. updating variance incrementally). For example, you want immediately responding visuals as you tweak some model parameters and need bounds on recomputation time or want to retain resources e.g. DOM SVG elements, WebGL context, buffers, shaders etc.

But I don't know how I'd do this well with Redux. Any thought on how to handle a cascading chain of dependent actions with Redux that's efficient? As an example: sure it's possible to reduce the state from a series of various events, and I suppose memoization can be used to avoid unnecessary recalculations. Downstream, regenerating and/or caching a vDOM tree and DOM diffing it the React or snabbdom way can even preserve key resources (e.g. DOM elements). But in my experience, when you write highly interactive visualizations or games - as opposed to writing a TodoMVC app, a newsstream SPA or other CRUD - it's hard to justify going down the route of memoization and DOM diffing when the alternative, a DAG based dataflow, can be smart about what changed, and no work is needed if the changes don't justify it.

For example, a new data point necessitates that the axis scale be recomputed if it's outside the current bounds, and this in itself leads to downstream changes (tweening the axis, maybe switching to coarser ticks, recomputing certain statistics, perhaps reducing the level of detail on the plot). But if the new point is inside the bounds, none of this needs to happen. With Redux, I only know of memoization and the shouldComponentUpdate trick to minimize performance loss, but maybe I'm not considering a more idiomatic Redux way?

In summary, is it not the case that FRP inspired libraries such as MobX, most.js, flyd, xstream, kefir can serve a class of applications that would be hard to do with Redux, besides the less limiting differences you enlist, e.g. OO or not; verbosity; style and feel?

AriaFallah commented 8 years ago

@monfera

Really well put, and a really good read. I found myself agreeing with everything you were saying as I read through.

mweststrate commented 8 years ago

Nice discussion and there are quite some external links to this. Doesn't need to remain open any longer I think. If somebody feels fur summarizing it in a blog post that would be :100: (I would be too opinionated for that ;-))

usergit commented 8 years ago

@mweststrate It would be great to leave it open so others can chime in as the community grows

mweststrate commented 8 years ago

Reopened :)

Kamaraju333 commented 7 years ago

@mweststrate can you explain how observable works internally and is state immutable in mobx?

mattruby commented 7 years ago

Mobx state is not immutable. Here's a video that goes over how mobx works : https://youtu.be/TfxfRkNCnmk there are many articles that go over how mobx works.

On Oct 17, 2016 10:47 PM, "Kamaraju prathi" notifications@github.com wrote:

@mweststrate https://github.com/mweststrate can you explain how observable works internally and is state immutable in mobx?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx/issues/199#issuecomment-254399415, or mute the thread https://github.com/notifications/unsubscribe-auth/AAIrctRRLdWyy37-04EHln2-tL1BJU3Qks5q1EFrgaJpZM4IKFcX .

Kamaraju333 commented 7 years ago

@mattruby Thanks

fourcolors commented 7 years ago

@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.

mattruby commented 7 years ago

Check out mobx-state-tree.

On Oct 31, 2016 11:06 AM, "Sterling Cobb" notifications@github.com wrote:

@mattruby https://github.com/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.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx/issues/199#issuecomment-257336956, or mute the thread https://github.com/notifications/unsubscribe-auth/AAIrcrAw_crcmVkcKi18jyPl22OimU9pks5q5hH9gaJpZM4IKFcX .

joelday commented 7 years ago

EDIT: Will rearticulate this later once less drunk.

mweststrate commented 7 years ago

@joelday regarding that last comment; MobX already has Knockout style computed observables? Or am I missing something here?

frankandrobot commented 7 years ago

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?

hccampos commented 7 years ago

@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).

capaj commented 7 years ago

@frankandrobot

Seems too early to tell.

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.