Skip to content
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

Is it different from MobX? #7

Closed
dai-shi opened this issue Aug 5, 2019 · 21 comments
Closed

Is it different from MobX? #7

dai-shi opened this issue Aug 5, 2019 · 21 comments

Comments

@dai-shi
Copy link
Owner

dai-shi commented Aug 5, 2019

I just found a comment by @FredyC in facebook/react#15156 (comment).

@fuleinist Ultimately, it's not that different from MobX, although a lot simplified for a specific use case. MobX already works like that (also using Proxies), the state is mutated and components who use specific bits of the state get re-rendered, nothing else.

I'm not very familiar with MobX, so please correct me if I'm wrong.
As far as I understand, MobX uses Proxies to trap object mutations, not object property access in render function. I mean, what it's providing is not an alternative to Redux/ReactHooks reducers.
Theoretically, MobX is complementary to react-tracked. I'm not yet sure if/how it can be implemented, though.
react-tracked doesn't change the semantics of render function. It tracks the state usage and trigger re-renders. How to update state is not the focus of the library (at least at the moment).

I wrote an example to show the power of state usage tracking in the "Advanced Example" in https://blog.axlight.com/posts/effortless-render-optimization-with-state-usage-tracking-with-react-hooks/.


(edit) There was a misunderstanding of mine. useObserver tracks the state usage.

@danielkcz
Copy link

danielkcz commented Aug 5, 2019

Well, MobX is definitely broader and can be used anywhere, not just with React. My comment was certainly somewhat far fetched for sure. You are right that you don't change the way how the state is mutated. MobX does that.

Also, you don't have a concept of observer which MobX currently requires so it's able to watch for reactions. Although we do have a useObserver which I assume works in a similar way, although slightly more complicated probably.

Your project definitely has its appeal for people who have code base with Redux (or similar) and want to optimize renders, but I don't think that mixing with MobX would bring any benefit. On contrary, it might be causing double re-render if used together.

@dai-shi
Copy link
Owner Author

dai-shi commented Aug 5, 2019

Thanks for your comment!

I think conceptually the observer pattern and this project can be combined. Maybe, it could be done with MobX core without react binding, but still not trivial (I mean react-tracked would need be modified for subscription.)

mixing with MobX would bring any benefit.

Yeah, that's what I expect. Probably, not because of technical issues, but mental model.

it might be causing double re-render

That's what I thought too, if it's done naively.

@dai-shi dai-shi closed this as completed Aug 5, 2019
@dai-shi
Copy link
Owner Author

dai-shi commented Aug 5, 2019

It reminds me of one other comment:
My hypothesis is if a developer thought React Redux is too difficult, and MobX is easy,
then they might like useTrackedState if reducers (and dispatching actions) are not blocker to them.
Would be great if you had any thoughts on it.

@dai-shi
Copy link
Owner Author

dai-shi commented Aug 5, 2019

Maybe, it could be done with MobX core without react binding

I think I was wrong. The notion of "Computed values" in MobX core might not fit well with useTrackedState.

(edit) Hmm, maybe not? I think I'd try to create an example.

@danielkcz
Copy link

danielkcz commented Aug 5, 2019

Yea, computed observables are a very powerful feature, I wouldn't want to live what that anymore :)

Personally, I love to use https://github.com/immerjs/immer when I need to tackle mutations to immutable state. Perhaps it would make sense for this project to join forces there. The Immer is from the same author as MobX and there are some rumors it might work much better in Concurrent React, at least for a local component state.

But generally, I do avoid reducer pattern, it's just too convoluted for my taste.

@dai-shi
Copy link
Owner Author

dai-shi commented Aug 5, 2019

Yeah, I'm sure immer works nicely with react-tracked and reactive-react-redux.

I need to tackle mutations to immutable state.

Hey, I have a question on this. Doesn't MobX update state immutably?

@danielkcz
Copy link

Hey, I have a question on this. Doesn't MobX update state immutably?

Um, quite opposite :) MobX always mutates and lets listeners know about the change. I mean it mutates primitive values, but objects, arrays are immutable. It's what I like about MobX the most, I don't need to think in terms of sCU, just use whatever I need to render and it will work.

@dai-shi
Copy link
Owner Author

dai-shi commented Aug 5, 2019

Oookay, I got that. It makes sense. I wonder how MobX/react works with React.memo then. Maybe not recommended?

I have another finding. autorun in MobX seems quite similar to the idea in react-tracked. Is it Proxy-based?

(edit) That applies to useObserver, too. OK, I misunderstood the huge part of MobX.

@dai-shi
Copy link
Owner Author

dai-shi commented Aug 5, 2019

Finally, created an example to combine mobx-react-lite and react-tracked.
https://codesandbox.io/s/jovial-moser-u3ikz

It's very tricky, and not meant to be a real thing. But, the idea is that while useObserver takes a render function as an argument, useTracked returns a state.

@danielkcz
Copy link

Uh...wow? :) You know, we were kinda fighting initially with this problem of needing observer around what's being rendered to keep track of it. What's the most surprising that it doesn't actually re-render more than necessary. Just for a convenience a modified version which logs renderings.

https://codesandbox.io/s/peaceful-swartz-cn280

@mweststrate Sorry, but I have to ping you in here, you have to see this :) ☝️

@mweststrate
Copy link

mweststrate commented Aug 5, 2019 via email

@dai-shi
Copy link
Owner Author

dai-shi commented Aug 5, 2019

this solution to create a proxy per render

Yeah.

one has to has proxies

We can do the same with object getters, no?
(getters have limitations compared to proxies, though.)

@dai-shi
Copy link
Owner Author

dai-shi commented Aug 6, 2019

https://codesandbox.io/s/blissful-breeze-jnsbo
Updated the example to use mobx directly without mobx-react-lite.
Unfortunately, probably because react-tracked expects immutable state,
we have separete state and store, but conceptually it should be combined.

@danielkcz
Copy link

Unfortunately, probably because react-tracked expects immutable state,
we have separete state and store, but conceptually it should be combined.

I wouldn't see it as a bad thing. On the contrary for people who are used to React.useState this might be more familiar to have tuple [state, setState]. Besides, it's not necessary to even return store there, it's just for convenience, but useContext can be used directly if state modification is required in a component.

@dai-shi
Copy link
Owner Author

dai-shi commented Aug 6, 2019

might be more familiar to have tuple

Agreed.

useContext can be used directly

Not sure if I understand this.


Anyway, out of my curiosity, I made another example.
https://codesandbox.io/s/vibrant-cray-m76dl
Now, it looks like:

const Counter1 = () => {
  const store = useObserver();
  console.log("render Counter1");
  return (
    <div>
      {store.count1}
      <button onClick={() => { store.count1 += 1; }}>increment1</button>
      {Math.random()}
    </div>
  );
};

This requires me to modify react-tracked a bit. branch
(No plan to release this, unless there are other use cases.)

This is not meant to be a real thing (nested objects may not work), but I hope you get the idea.
You might notice it's triple proxies. (1. mobx proxy, 2. snapshot proxy, 3. react-tracked proxy)

Look forward to your comments. @FredyC @mweststrate

@danielkcz
Copy link

danielkcz commented Aug 6, 2019

useContext can be used directly

Not sure if I understand this.

Well, in your initial implementation you were using useLocalStore to create a store, but usually, app state is in the context so the user could access it without react-tracked being involved for state mutations. But it could lead to bugs when you would use a property from a store directly for rendering purposes as well.

And this is also my concern. The react-tracked is exposing its own Provider which seems its kinda part of logic. However, we are currently trying to teach users they can use their own Context without any extra abstractions? I assume it could be extracted into a hook so users could use it with their own Providers, but still, it's somewhat constraining imo.

Nonetheless, your latest attempt certainly has an appeal. It's kinda funny because we were attempting a somewhat similar approach by utilizing babel-plugin-macros (rather awkward in retrospective). It's a nice surprise that something like that is possible in runtime.

I think that if we are going to pursue this direction, it would probably make much more sense to implement it natively without dependency on react-tracked, but mentioning it as an inspiration. So there is certainly no need to modify your lib to support MobX.

@dai-shi
Copy link
Owner Author

dai-shi commented Aug 6, 2019

more sense to implement it natively

Yeah, it wouldn't require triple proxies. 😄

teach users they can use their own Context

I see. It makes sense. It's possible because store is mutable and referentially equal.

exposing its own Provider

react-tracked is so tied to react local state and the state is updated immutably. So, I can't simply use the bare Provider/useContext which would trigger all consumer components to re-render.

By the way, I extracted the context part (no tracking support) from react-tracked and created another library.
https://github.com/dai-shi/use-context-selector

Another reason I use own Provider (special context) is for concurrent mode. Surely, subscriptions work in concurrent mode with the technique like useSubscription, but it "de-opts to sync mode." My special context doesn't have the drawback, and I feel using context cleaner. If useContextSelector were to be in react core, it would make more sense.

This leads to my experiment.
https://github.com/dai-shi/will-this-react-global-state-work-in-concurrent-mode
It doesn't include mobx yet. 😓 As I learned it a bit, I'd try to add mobx-react-lite@next.

@danielkcz
Copy link

danielkcz commented Aug 7, 2019

react-tracked is so tied to react local state and the state is updated immutably. So, I can't simply use the bare Provider/useContext which would trigger all consumer components to re-render.

Makes sense. That's what so great about MobX, because it has stores with stable reference, the Provider won't even re-render in most cases. Only components accessing variables that have actually changed will render.

Recently, I wanted to use Formik V2, but there are so many issues related to immutability and that everything is recreated on each keystroke. I ended up writing my own form solution based on MobX and it's awesome. It's not for public consumption though (yet). I even wrote an article about it some time ago ... https://levelup.gitconnected.com/formik-with-react-hooks-and-mobx-1493b5fd607e

That selector hook is definitely useful for immutable state, but in case of MobX not so much :)

About your experiment, that's something that I am afraid of too even for MobX, because useLocalStore might suffer there. The mobx-react-lite@next basically takes care of proper cleanup of reactions that were never committed, but it doesn't solve a problem of local states.

@avkonst
Copy link

avkonst commented Aug 16, 2019

Hi @FredyC
I have attempted to put Mobx benchmark test similar to this one for Hookstate, but it seems Mobx would rerender the full table when a cell value is set:

(I read it here: https://mobx-react.js.org/observer-hook Despite using useObserver hook in the middle of the component, it will re-render a whole component on change of the observable. If you want to micro-manage renders, feel free to use or useForceUpdate option (for advanced users).)

I would like to compare Mobx performance with Hookstate performance using this benchmark. I wonder if you could advise me how Mobx store should be used correctly, so only the single table cell is rerendered when it is set, but not the whole table?

@avkonst
Copy link

avkonst commented Aug 16, 2019

Also, @FredyC , I have noticed you had problems with large formik forms and Mobx rerendering whole form on each keystroke. Here is the solution this this problem using Hookstate: https://hookstate.netlify.com/local-multiple-consumers
I am interested in to know what you think about it. Is it comparable with what you have done with custom Mobx code? Thanks,

@danielkcz
Copy link

@avkonst This is hardly a place to get help with MobX :) Please open issue at https://github.com/mobxjs/mobx-react

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants