mobxjs / mobx-react-lite

Lightweight React bindings for MobX based on React 16.8 and Hooks
https://mobx.js.org/react-integration.html
MIT License
2.13k stars 90 forks source link

Get rid of (almost) all utility hooks #94

Closed danielkcz closed 5 years ago

danielkcz commented 5 years ago

Update

After a lengthy discussion, we have agreed on the removal of useComputed and useDisposable. The useObservable hook will be renamed to useAsObservableSource and meant primarily for turning props/state data into observable. These can be then safely used in a new hook useLocalStore which will support lazy-init and serve as the main way of constructing observables within a component.

Check out a comment with an initial proposal: https://github.com/mobxjs/mobx-react-lite/issues/94#issuecomment-482533778


Time has come to start considering this. There have been some discussions lately that got me convinced these utilities should not have been in a package in first place. I think we got carried away in here, most likely because it felt good to have some custom hook 😎 Ultimately, people might get the wrong idea that to use MobX in React they need to use these specific hooks.

I think it's better to do this sooner than later before people start using these utilities too much and would need to painfully migrate later.

As the first step, I would like to focus on preparing a website dedicated to MobX in React that would show several recipes and DIY solutions. I would like to hear some recommendations on what toolkit would be the best candidate for such a site. Gatsby?

The idea of some separate package with utility hooks is not eliminated, but it should go hand to hand with new docs to thoroughly explain the concepts and not to just blindly use the hook without fully understanding implications.

useComputed

The most controversial and not even working properly. After the initial pitfalls, I haven't used this anywhere. Is someone using it successfully?

useDisposable

As @mweststrate discovered today, there is not much of the benefit to this as the React.useEffect does exactly the same. The only difference is access to early disposal function. Personally, I haven't used for anything just yet. I would love to hear use cases if there are any. I am almost ashamed I haven't realized this before and just blindly used it 😊

useObservable

Probably the most useful out of these three and the most discussed (#72, #7, #22, #69) also. It's clear that it's not only confusing but in its current form, it's wrong altogether. Personally, I have rather used React.useState for a component local state which is so easy and doesn't require any observer. There is not much performance gain anyway unless <Observer /> is used. For the shareable state, it seems better to just use Context and build such a state in a way people like. Also, it makes a little sense to be resetting the whole MobX state based on props change.

joenoon commented 5 years ago

Fair enough, thanks for checking it! I think I'm trying to get at something a bit different that maybe isn't best in this thread. The reason it doesn't work is clear to us in that simple example, but in practice this is the type of thing I know I'll be seeing bug after bug in and having to constantly explain/fix. I think the philosophy of "when in doubt use observer" etc from mobx-react is a great approach - it eliminates a whole category of bugs. So I guess to sum it up, yes this all technically works as designed, but I was attempting (maybe poorly) to get at simplicity... i.e. should those patterns where you really have to know what you're doing be reserved for lower-level areas or do we want to be encouraging all those patterns throughout an entire app.

danielkcz commented 5 years ago

Ok, I think we got too much off the track here. We can generally agree that all three variants of the observer have some use cases and should be kept. It's merely the question of establishing some patterns and recipes which I want to tackle in #115, but could use some help for sure as I am kinda swamped with other stuff.

mweststrate commented 5 years ago

Fair point, it's all unrelated to the utility hooks :)

danielkcz commented 5 years ago

So I just recently discovered an interesting platform for investigating pros & cons. Made one regarding observer pattern, so let's try that :)

https://www.kialo.com/differences-with-mobx-observer-pattern-in-react-28353

danielkcz commented 5 years ago

Merged and published #130 in 1.3.0 version

Accepting PR for 2.0 removing these deprecated hooks.

sheerun commented 5 years ago

I'm looking at implementation of useAsObservableSource and it seems concerning that new observable is created for each instance of stateful component: https://github.com/mobxjs/mobx-react-lite/blob/master/src/useAsObservableSource.ts#L11

It it really necessary? What about using something like autorun instead?

olee commented 5 years ago

useState will run this function only once, because it's an initializer! So there's no issue with that.

sheerun commented 5 years ago

This is only within one instance of stateful component. I'm talking about multiple instances on page or multiple different components using the same data source

danielkcz commented 5 years ago

This is only within one instance of stateful component. I'm talking about multiple instances on page or multiple different components using the same data source

Well, that's kinda up to you. If you want to optimize that way, simply store that state inside the context and reuse across the components.

sheerun commented 5 years ago

Then what's the point of useAsObservableSource if it only allows for tracking changes within one instance of stateful component? We already have useState for it (or useLocalStore)

danielkcz commented 5 years ago

It's useful in conjunction with useLocalStore if you have some values derived from a combination of props and observable state. Read the discussion above, there is brainstorming around this, especially https://github.com/mobxjs/mobx-react-lite/issues/94#issuecomment-482533778

JabX commented 5 years ago

Then what's the point of useAsObservableSource if it only allows for tracking changes within one instance of stateful component? We already have useState for it (or useLocalStore)

If you already have observable data (from a prop, from context or even just a plain "global" observable object), you don't need useAsObservableSource and changes will be tracked as they always have by observer/<Observer>/useObserver or any reaction that you may declare in your component.

This hook's goal is to create an stable (= will not change throughout the life of the component) reference to an object that you create from unstable (= will change at each render) references. The main usage here is to wrap props, so that you can use them in useLocalStore and local reactions created with useEffect without having to recreate them at each render to accommodate to prop changes. This is how hooks are expected to work in general, but this behaviour doesn't work well (and flat out doesn't make any sense with observable data), so we had to find a workaround

sheerun commented 5 years ago

I've slept on it and I think that introducing useAsObservableSource was a mistake if its primary purpose is supporting useLocalStore. A more "react hooks" way would be to pass property dependencies as second argument as so:

    const store = useLocalStore(() => ({
        count: 0,
        get multiplied() {
            return multiplier * this.count
        },
        inc() {
            this.count += 1
        }
    }), [multiplier])

I also believe that react does this convention on purpose because they plan to extend compiler support (just like newest version of svelte does). So while you might consider such "manual" dependency declaration unpleasant, it isn't necessairly forever as mobx-react-lite could introduce in the future babel plugin that auto-injects property dependencies into useLocalStore as well

mweststrate commented 5 years ago

As discussed above, the problem with such approach is that you don't want to reset count when multiplier changes.

Op di 30 apr. 2019 18:19 schreef Adam Stankiewicz <notifications@github.com

:

I've slept on it and I think that introducing useAsObservableSource was a mistake if its primary purpose is supporting useLocalStore. A more "react hooks" way would be to pass property dependencies as second argument as so:

const store = useLocalStore(() => ({
    count: 0,
    get multiplied() {
        return observableProps.multiplier * this.count
    },
    inc() {
        this.count += 1
    }
}), [multiplier])

I also believe that react does this convention on purpose because they plan to extend compiler support https://twitter.com/sebmarkbage/status/1122185602839932928 (just like newest version of svelte 3 https://svelte.dev/blog/svelte-3-rethinking-reactivity does). So while you might consider such "manual" dependency declaration unpleasant, it isn't necessairly forever as mobx-react-lite could introduce in the future babel plugin that auto-injects property dependencies into useLocalStore as well

β€” You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx-react-lite/issues/94#issuecomment-488018042, or mute the thread https://github.com/notifications/unsubscribe-auth/AAN4NBDPHVN2PD34U2Y2DKLPTBWRLANCNFSM4HAQXQOQ .

sheerun commented 5 years ago

It doesn't need to you if implement it this way. This just API change

sheerun commented 5 years ago

Okay maybe I'm saing things that can't be implemented. Next time I'll support my comment with code

danielkcz commented 5 years ago

@sheerun In case you would make it like a dependency, there is no way for you to apply that changed value except throwing away whole observable and creating a new one. We would need to provide a way for you to actually merge with the previous state.

For someone who is starting with MobX you are full of ideas :D

sheerun commented 5 years ago

I don't know from where the impression that I'm starting with mobx. It's almost 3 years for me..

urugator commented 5 years ago

@sheerun Also note that: Mobx is all about automatic subscription management (figuring deps out for you).
Deps array is (optional) optimization tool, not a semantic guarantee - that wouldn't be true in this case as missing deps could cause staleness. If compilation becomes a necessity, more profound solutions are possible (like svelve, elm, effectfuljs...).

sheerun commented 5 years ago

142 Introduces what I'm telling about without "resetting counter" and without "throwing away whole observable and creating a new one"... little faith please

mweststrate commented 5 years ago

@sheerun apologies, your earlier code snippet suggested / implied a full reset to me without further explanation. The PR is much clearer :).