Closed mweststrate closed 5 years ago
To summarize (and check if I remember the setup correctly)
observer
component will always pass a new closure to useObserver
each time it renders. Without this, changes in the props wouldn't be picked up*. observer
component is memo
, because otherwise it would always rerender if a parent rerenders, and that rerender would always cause the useObserver
to re-render as well (due to the new closure). In other words, if it wasn't a memo, then toggling an observable value in the root component would cause the entire component tree to re-render.N.B. theoretically <Observer>
could be memoized as well, as it might receive the same closure over and over again (this happens if observer
is used on a class component for example). But since observer
itself is already memo
, there is no real need; if the class component has a change in state / props, it will re-render anyway.
I think that's correct. In theory we could have useObserver(render, [prop1, prop2])
returning previously rendered element directly if prop1/prop2 haven't changed and reaction isn't dirty...
But closure is always recreated, that's the property of hooks (supposedly without significant impact).
this link is to a very small wrapper to use hooks with component classes https://github.com/kesne/with-react-hooks/blob/master/src/index.tsx
it may be useful to use something similar to unify the interface of observer((props) => , either as as like 6.0.0.bridge or even as a permanent solution.
idk if this has been considered or not, but its a pattern i've been using recently to refactor from component classes
ex: this is after a refactor
//this isnt a react anything even though the content field has react related data
class HostLinkValue {
href: any;
content: any;
_handleClick: (e: any) => void;
constructor(props){
this._handleClick = (e) => {
e.preventDefault();
props.onClick && props.onClick(),
props.host.open && props.host.open(props.request).then(openedHost => {
if(props.onHostOpened) {props.onHostOpened(openedHost) }
}),
props.host.load && props.host.load(props.request)
}
this.href = props.host.getUrl(props.request);
this.content = React.Children.count(props.children) > 0 ? props.children : props.title
}
@action
click = e => this._handleClick(e)
}
export const AppLink = observer((props) => {
const link = new HostLinkValue(props)
return (
<a
style={{color: 'blue'}}
className={props.className}
title={props.title}
href={link.href}
onClick={link.click}>{link.content}
</a>
);
}
)
using this pattern I can move pretty much everything except lifecycle hooks outside of react , which for me is a win - and if used with the component wrapper above pretty much covers everything with minimal effort.
maybe instantiating the stateful class outside react then passing it as a parameter to useEffect wouldn't require any observer wrapper at all?
no idea if this would work , seems like it would though lol - useEffect is basically autorun right? and the second param is like only update for keys
import {useEffect} from 'react'
export const AppLink2 = (props) => {
var link = new HostLinkValue(props)
useEffect(() => void 0, [link])
return (
<a
style={{color: 'blue'}}
className={props.className}
title={props.title}
href={link.href}
onClick={link.click}>{link.content}
</a>
);
}
Tldr, is mobx react even needed now? React has the mechanics to handle reactions to data.
The biggest thing to keep is inject , but maybe it should move to the mobx library now and just inject inside the constructor?
React has the mechanics to handle reactions to data.
Care to elaborate on what mechanics it has? If you are pointing out to Context, that's far from ideal as it will always re-render a whole tree starting at the Provider.
The biggest thing to keep is inject , but maybe it should move to the mobx library now and just inject inside the constructor?
The biggest? :) On the contrary, it got super easy with Context to have mobx stores around.
https://github.com/mobxjs/mobx-react-lite#why-no-providerinject
1) useEffect is very similar to autorun - callbacks on changed data, specific property watchers, it returns a disposer. pretty much the same. It rerenders the component on data changes. As long as the data changes are efficient (via mobx) the renders should be too, right? you can also use it in conjunction with useRef for any object not just dom elements.
1.1) the one thing that useEffect doesn't do it is provide a mechanism to directly call forceUpdate - maybe they were worried about self-referencing effects or something? who knows. I think the current workaround just uses an empty useState call, but maybe a similar behavior could come from using a dummy variable on the object you pass into useEffect?
2) dependency injection isn't limited to react components nor is mobx limited to react - imo one of the best things about mobx is you can easily share stores between rendering libraries. I also don't really think context is an ideal use for mobx stores now with the other hooks available.
I think at its core, mobx is designed to efficiently and reactively update data. react now has a set of reactive hooks to run updates on the views after data changes
i guess I don't understand what the need is now?
The docs say:
Subscribe to this issue for a proper migration guide.
What I see here is long discussion about the future of mobx-react. As interesting as this is, where do I go to learn how to use mobx with functional (instead of class) components? What does useObservable
do that useState
doesn't? When/how do I use @action
or action
or runInAction
? The API is laid out pretty well in the docs, but I'm a little confused which circumstances to use mobx vs. just plain react state.
@devuxer Yea, migration guide is still kinda in the wind, sorry about that :)
Regarding your state questions have a look at https://github.com/mobxjs/mobx-react-lite/issues/69
If you have further questions regarding the use of mobx in functional components, feel free to open issue at mobx-react-lite repo.
@Jeremy I think the big point you are missing is that React can do all these things only with the state that is owned by the same component. However, you cannot run a useEffect etc etc on state that doesn't live in the component. So fundamentally nothing really changed, for purely local state, one could always already use setState + lifecycle hooks. That changed now to hooks / effects, but it still the same scope. The real value of mobx kicks in as soon as you have state stored outside the component / in different components, or complex derivations come into play.
On Sat, Mar 9, 2019 at 8:41 PM Daniel K. notifications@github.com wrote:
@devuxer https://github.com/devuxer Yea, migration guide is still kinda in the wind, sorry about that :)
Regarding your state questions have a look at mobxjs/mobx-react-lite#69 https://github.com/mobxjs/mobx-react-lite/issues/69
If you have further questions regarding the use of mobx in functional components, feel free to open issue at mobx-react-lite https://github.com/mobxjs/mobx-react-lite repo.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx-react/issues/640#issuecomment-471215319, or mute the thread https://github.com/notifications/unsubscribe-auth/ABvGhPH1tAgwh5bEU7empcUwvwKwPWOiks5vVA5qgaJpZM4amxi7 .
Wrong @jeremy, FYI. On Tue, Mar 12, 2019 at 09:05 Michel Weststrate notifications@github.com wrote:
@Jeremy I think the big point you are missing is that React can do all these things only with the state that is owned by the same component. However, you cannot run a useEffect etc etc on state that doesn't live in the component. So fundamentally nothing really changed, for purely local state, one could always already use setState + lifecycle hooks. That changed now to hooks / effects, but it still the same scope. The real value of mobx kicks in as soon as you have state stored outside the component / in different components, or complex derivations come into play.
On Sat, Mar 9, 2019 at 8:41 PM Daniel K. notifications@github.com wrote:
@devuxer https://github.com/devuxer Yea, migration guide is still kinda in the wind, sorry about that :)
Regarding your state questions have a look at mobxjs/mobx-react-lite#69 https://github.com/mobxjs/mobx-react-lite/issues/69
If you have further questions regarding the use of mobx in functional components, feel free to open issue at mobx-react-lite https://github.com/mobxjs/mobx-react-lite repo.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <https://github.com/mobxjs/mobx-react/issues/640#issuecomment-471215319 , or mute the thread < https://github.com/notifications/unsubscribe-auth/ABvGhPH1tAgwh5bEU7empcUwvwKwPWOiks5vVA5qgaJpZM4amxi7
.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx-react/issues/640#issuecomment-472060693, or mute the thread https://github.com/notifications/unsubscribe-auth/AAAAx9R56zU0c4UHH_wS8v42Cy3ViWuRks5vV9BegaJpZM4amxi7 .
ah, thanks michel, i was under the impression useRef had changed to let you reference outside objects not just dom components, but... very simple example here doesn't work :\
import * as React from "react";
import {observable, action} from 'mobx'
import {useRef, useEffect, useCallback, useState} from 'react'
class CounterStore {
@observable count
constructor(){this.count = 1}
@action inc = () => this.count ++ && console.log('got inc', this.count)
@action dec = () => this.count --
}
const counterStore = new CounterStore()
const MobxCounter = () => {
var counter = useRef(counterStore)
return (
<div className="counter">
<p>Mobx: You clicked {counter.current.count} times</p>
<button onClick={counter.current.inc}>Click me</button>
</div>
);
};
//also doesnt work
function MobxCounter2(){
var counter = () => useRef(counterStore)
return (
<div className="counter">
<p>Mobx: You clicked {counter().current.count} times</p>
<button onClick={counter().current.inc}>Click me</button>
</div>
);
};
@jeremy-coleman You can store objects in useRef
, but they are not reactive in any way on its own.
Any component can basically re-render itself only with useState
or useReducer
hooks. The useEffect
you talked about before (but not using in the example) cannot do much on its own, it's for side-effects that can be run based on variable changes. You should probably read Hooks docs more throughly, seems you are missing basics :)
MobX for React is not some magic box either. It utilizes forceUpdate
(in classes) or useState
(in fc) to re-render the component when it sees a change to an observable variable. And that's the role of observer
which can track such observable variables. Modify example like this and it will work just fine.
const MobxCounter = () => {
var counter = useRef(counterStore)
return useObserver(() => (
<div className="counter">
<p>Mobx: You clicked {counter.current.count} times</p>
<button onClick={counter.current.inc}>Click me</button>
</div>
));
};
Your second example is weird. For every call of counter()
you would get another ref, so there would be two refs with the exact same reference to the same store. It would work with useObserver
, but I don't really recommend doing it like that.
thanks for the example freddy, i know the code i posted wasn't proper , I tried about every combination of useEffect , useRef and useState but the view would never update. react docs seem misleading saying: The useRef() Hook isn’t just for DOM refs. The “ref” object is a generic container whose current property is mutable and can hold any value, similar to an instance property on a class. (aka an injected mobx store) and even in the effect example.
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
I realized later it's probably due to the combination of react trying to be lazy causing mobx to not see the value as being observed
@jeremy-coleman This is very offtopic, but where do you see "misleading part"? It is true that you can store anything to the ref
, but it won't re-render component when you change the value.
The useEffect
will execute when it's dependencies change, but it's not reactive. The component itself must re-render first for the useEffect
to be evaluated again and compare dependencies to decide if contained side effect should be executed. The useEffect
is not meant as a primary way to re-render component unless you change the state.
Please, if you have further questions, use either https://gitter.im/mobxjs/mobx or https://spectrum.chat/mobx-state-tree or even better https://spectrum.chat/react
from the react docs: 1)"This makes it (useEffect) suitable for the many common side effects, like setting up subscriptions and event handlers" 2)"By default, effects run after every completed render, but you can choose to fire it only when certain values have changed". I mean they literally use a subscription in the example. i understand why it doesn't work though now
Discussing either useEffect
or React versus MobX in general is totally offtopic in this thread. It is long enough as it is without that. Please continue this discussion elsewhere :)
@urugator
It also makes mobxjs/mobx#1811 impossible to implement, because we don't have an access to reaction in SCU, correct?
Observer implementation still have access to it's own reaction.
@mayorovp Just to clarify. I meant this impl https://github.com/mobxjs/mobx-react/blob/v6-radical/src/observer.js
It uses <Observer>
respectively useObserver
hook, which holds the reaction reference ... so the hook has to somehow expose the reaction to the parent class based component with the access to SCU.
Perhas it's easily doable, I don't know, it just occured to me, because the reaction is no longer created by class component...
mobx-react@6.0.0-rc.2
is now available!
Could someone give it a quick try on react-native?
@mweststrate I am curious why have you decided to keep JavaScript code and have a separate typings? Is there some benefit I am not seeing? It feels strange considering that mobx and mobx-state-tree are both full TypeScript.
Point is that I am mostly getting used to the idea that mobx-react-lite
will eventually become obsolete an part of this module. However, I would definitely like to keep TypeScript in the code and that makes merging those together rather problematic.
No, just too lazy to migrate 😊
Op vr 22 mrt. 2019 18:40 schreef Daniel K. notifications@github.com:
@mweststrate https://github.com/mweststrate I am curious why have you decided to keep JavaScript code and have a separate typings. Is there some benefit I am not seeing?
Point is that I am mostly getting used to the idea that mobx-react-lite will eventually become obsolete an part of this module. However, I would definitely like to keep TypeScript in the code and that makes merging those together rather problematic.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx-react/issues/640#issuecomment-475714193, or mute the thread https://github.com/notifications/unsubscribe-auth/ABvGhPd_KmdI8oYyl0Qva_jBjP-BHxelks5vZRWmgaJpZM4amxi7 .
Ok, I guess we can migrate it after V6 comes out, it's not going to be breaking change or anything. I am willing to help with that eventually.
Btw, about the auto-memo discussion above. I actually got burned by that. There is a problem with the Context. If its value changes, the tree under the Provider gets re-rendered. The memo
will serve as a bouncer of such change. No component below the observer
would be re-rendered because props did not change. I had to switch to useObserver
after some hour of being annoyed what's happening.
The obvious advice is to have an observable in the Context, but this more of the legacy code concern which uses old react-form
package. I assume there are still other valid libraries with such approach that people would use and they would get burned by it.
I am not sure if it's enough for getting rid of the memo
, but it's not ideal in its current form either.
@FredyC Can you share an example? memo
doesn't block context changes.
The legacy context updates are broken and de facto forbidden
@urugator You are right, seems the issue of the old context actually. I guess I have to get rid of that legacy react-form
in a first place :) False alarm then, at ease :)
@mweststrate & @FredyC, I tried out mobx-react@6.0.0-rc.4
today. Everything works fine, but I fail to see where I could use any hooks ported from mobx-react-lite (it is not clear what was carried over). In fact, I found the use of <Observer>
also very awkward. Details below refer to my GitHub repo. Please let me know what I am missing and how I can better leverage the new functionality.
useEffect()
. TransactionsPage is essentially my "smart" component responsible for fetching data.accountStore
) to the AccountsPanel
. AccountsPanel is a "dumb" component responsible for rendering the accounts.observer()
to make sure it reacts to account changes. Note that I tried to use <Observer>
first, but it had to be used in two different places to cover accountStore.loading
and accountStore.accounts
. Wrapping the entire component was much easier.Please let me know how this code can benefit from v6 features. TIA.
@nareshbhatia Seems like you are a bit confused. V6 does not really bring any new essential features on the table. It's pretty much a starting point where class and functional (with hooks) components are supported in a single package. It uses mobx-react-lite
underneath for a hooks support with a fallback to "old behavior" to support classes.
You can just keep using observer
HOC if you like or useObserver
hook if that's more up to your taste. The <Observer>
is surely awkward to use once someone gets a taste of hooks, so unless you like it, you don't need to use it.
I had no time to analyze your use case, just know that major breaking change is a need for React 16.8. Everything else should be backward compatible. And some deprecated stuff got removed, but that's normal.
@FredyC, thanks for the clarification on the purpose of V6. This is such a long thread that I could not easily locate it.
In any case, the purpose of my repo is to demonstrate the latest best practices in the React ecosystem. Hence I am using function components everywhere and hooks wherever they makes sense (useContext, useEffect, useStyles). If you don't have much time to review in detail, please just look at AccountPanel. I think this a case where the observer()
hoc ends up in smaller code than using useObserver()
twice. TIA.
@mweststrate
I've tried the latest v6 rc.4 (with mobx 4) on a NextJS project that requires some IE11 support and it is not working anymore in some cases.
The issue I had was that one page was not firing any onClick events in IE11. I tracked down the issue to an inject
: removing it made the clicks fire again. I have absolutely no clue why this page in particular was behaving this way considering I have other pages with inject
working perfectly fine. Downgrading to 5.4.3 solved the issue.
The smallest reduction I did was:
@inject(({ rootStore }) => ({
annotationStore: rootStore.annotationStore
}))
class MyPage extends React.PureComponent<{}> {
render() {
return <button onClick={()=> console.log("clicked")}>Click me</button>;
}
}
Again, the same code worked for other pages so I am not really sure what is the root cause there but downgrading does fix the issue.
@Keats It would really help if you would manage to put together working reproduction, ideally in https://codesandbox.io. Otherwise, we are probably clueless as much as you are if it's happening only in a specific case in your app.
I spent about 30 minutes trying to reproduce it in codesandbox with no luck :/ I'll post it here if I manage to do so
I have 6.0.0-rc.4 running on a couple internal apps and things seem to be solid enough. Many components were migrated from another application and converted from class to functional components pretty seamlessly. I do think there is room for improvement on the documentation but overall I am happy. Thanks for the hard work.
Great thread, read the whole thread. But didn't find if there will be IE11 supported version for hooks? Unfortunately our client has many other old applications and new applications must support IE11.
Hooks and IE11 are unrelated, and IE 11 is supported as long as you stick to mobx(!) 4, which will be compatible with mobx-react 6
Op vr 5 apr. 2019 10:24 schreef asaarnak notifications@github.com:
Great thread, read the whole thread. But didn't find if there will be IE11 supported version for hooks? Unfortunately our client has many other old applications and new applications must support IE11.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx-react/issues/640#issuecomment-480175227, or mute the thread https://github.com/notifications/unsubscribe-auth/ABvGhLIJ63QTTlnh2VOSmJsnr2m7CLujks5vdvodgaJpZM4amxi7 .
If i stick to mobx@4 will new features described here be in mobx@4 ?
@asaarnak Which part of this sentence you did not understand? :)
mobx(!) 4, which will be compatible with mobx-react 6
Sorry, i see now. :)
N.B. release has been postponed a bit, until we've found the most optimal API for combining observables with hooks. Which is not really a technical problem, but we want to make sure there aren't to much caveats or confusing variations in the api. Feel free to chime in! https://github.com/mobxjs/mobx-react-lite/issues/94
In the mean time, if you are still on mobx-react@5, just continue happily throwing in <Observer>
into your hook based components :)
Thanks for the update! Really looking forward to the release. Been using the beta version for a while and it hasn't caused any problems. Only annoyance with hooks is the inability to see values for them in dev tools but that's obviously not a mobx issue.
Hi all,
I have mobx-react@6.0.0-rc.4
and Next.js@8
I don't use both @inject()
and static getInitialProps
at the same time.
getInitialProps
don't execute when @inject
has been used.
@nghiepit Um, what is getInialProps
? Do you mean getDefaultProps
or getInitialState
? Can you provide reproduction in either repo or CodeSandbox showing the problem?
In any case, best report problems in a separate issue, including the normally required information such as a reproduction
On Tue, Apr 30, 2019 at 11:12 AM Daniel K. notifications@github.com wrote:
@nghiepit https://github.com/nghiepit Um, what is getInialProps? Don't you mean getDefaultProps? Or getInitialState? Can you provide reproduction in either repo or CodeSandbox showing the problem?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx-react/issues/640#issuecomment-487874602, or mute the thread https://github.com/notifications/unsubscribe-auth/AAN4NBAXLSXSRHKKN4LZVUTPTAERHANCNFSM4GU3DC5Q .
@FredyC @mweststrate My CodeSandbox https://codesandbox.io/embed/github/nghiepit/next-mobx-bug-report/tree/master/?fontsize=14&module=%2Fpages%2Fother.js
@nghiepit There is no known getInitialProps
static method in React, you probably mean getDefaultProps
. You got confused there. Please if you have further questions do as @mweststrate said, open the new issue with all relevant information.
@nghiepit For whatever reason getInitialProps
doesn't seem to be hoisted, so you have to define the function on exported Injector
: https://codesandbox.io/s/2o08jqxlnp
Please create a seperate issue if you have further questions.
EDIT: I see, you're using v6, which no longer hoists statics (it's actually mentioned in the first comment...)
@FredyC I'm using Next.js
and getInitialProps
is the helpfull function in Next.js
Thanks to @urugator I will temporarily hot-fix like you.
Hope, It will fix soon.
@nghiepit so far there are no plans to explicitly hoist statics (it would be could to mention the above work-arounds in the docs). If that is a problem and you would like to challenge it, please open a separate issue, inside this conversation the problem / discussion will get lost
@mweststrate I experimented with a simple context based hook that fulfills the role of the current inject
functionality and used it along side the <Observer>
component from mobx-react (not *-lite). However, it's not not picking up state updates:
export const StoreContext = React.createContext({});
export function useStore(mapActions) {
const globalStore = useContext(StoreContext);
let store;
if (typeof mapActions == 'string') {
store = globalStore[mapActions];
} else {
store = mapActions(globalStore);
}
return store; // I also tried `return observer(store)`
}
Usage as follows: Injecting the Context into the app
import * as stores from '../stores'; // imports all instances of mobx stores
<StoreContext.Provider value={stores}>
{children}
</StoreContext.Provider>
Sample Component showing usage:
const MyComponent = () => {
const profileStore = useStore(stores => stores.profileStore);
const { profile } = profileStore;
return <Observer>
{() => (<div>Name: {profile.name}</div>)}
</Observer>
};
So I'm wondering if inject has some magic behavior under the hood other than just injecting stores that my useStore
implementation is missing.
@simo-eskalera
it's not not picking up state updates
Updates of what exactly? All observables must be accessed from within observer/Observer/useObserver
. So the modifications of stores.profileStore
and profileStore.profile
won't be picked, only profile.name
will.
@urugator nvm, I wrote a demo and it worked fine there: https://codesandbox.io/s/w7y0180w0k
I wonder if we have an equivalent to useStore
, like the one I have in the demo link. Usage makes it easy to immediately understand what you're getting. Here's some flavors:
// Specified as a string
const timerStore = useStore("timerStore");
// Specified as a function
const timerStore = useStore(stores => stores.timerStore);
// deconstruction replaces direct injection of store props via `inject(mapToProps)` function
const { timer, reset } = useStore("timerStore");
Things to be addressed:
<Observer>
ormobx-react-lite
)hoist-non-react-statics
?