Replies: 63 comments
-
I managed to strip down example to bare React + mobx-react-lite sandbox link I'm pretty sure this happens due to the assignment during render in this line which triggers reaction and eventually setState I tried moving this assignment into the React.useEffect(() => {
runInAction(() => {
Object.assign(res, current)
})
}) However this increases render count and breaks some tests. I don't have enough inside knowledge about I've created a draft PR to try to fix this issue. Also while running tests for it I found a potential bug in |
Beta Was this translation helpful? Give feedback.
-
To be honest, I am not sure either. This new flashy warning is causing havoc all around and it's more confusing than helpful in my opinion.
That sounds safe to me, even if observable changes during render phase, the reaction and It's definitely curious why your fix helped to remove that warning. Either it's some coincidence or there is actually something it. My expertise isn't that great either. |
Beta Was this translation helpful? Give feedback.
-
Wait, I totally missed a reproduction from @SidKwok before, sorry about that. But looking into that, there is something smelly in here. const Parent = observer(() => {
const [value, setValue] = useState({ current: 0 });
const store = useLocalStore(
source => ({
get current() {
return source.current;
},
increment() {
setValue(prev => ({ current: prev.current + 1 }));
}
}),
value
);
return <Child store={store} />;
}); First of all, why are you combining |
Beta Was this translation helpful? Give feedback.
-
Also it is curious that the warning shows up only for the first click but not for any subsequent clicks Regarding |
Beta Was this translation helpful? Give feedback.
-
Well, that's more of the bug in user code. The |
Beta Was this translation helpful? Give feedback.
-
Also I do agree mutating something outside of observable scope which needs to observed for some reason is conceptually wrong |
Beta Was this translation helpful? Give feedback.
-
@SidKwok closing the issue as it seems that your pattern is simply wrong. I will reopen if you have something else to add here. |
Beta Was this translation helpful? Give feedback.
-
@FredyC |
Beta Was this translation helpful? Give feedback.
-
@FredyC |
Beta Was this translation helpful? Give feedback.
-
You are right there is something strange to your use case. I stripped down your example to remove any "weird patterns" and the issue can still be reproduced. https://codesandbox.io/s/quirky-taussig-qfwyc?file=/src/App.js It does really seem that The strangest part is that it does happen only when @mweststrate Do you have some ideas here? There was attempted PR mobxjs/mobx-react-lite#280 to run |
Beta Was this translation helpful? Give feedback.
-
Digging further and I removed react-router from the equation and the issue can be still reproduced even more reliably after every page reload (because the empty string is initial value). https://codesandbox.io/s/festive-currying-498ny?file=/src/App.js It almost seems that MobX does something special when an empty string is passed to |
Beta Was this translation helpful? Give feedback.
-
I think the general approach is to |
Beta Was this translation helpful? Give feedback.
-
@nulladdict That's pretty much irrelevant, the |
Beta Was this translation helpful? Give feedback.
-
What I meant is that it’s probably better to not observe state managed by something else, just like in my Also empty string probably has nothing to do with a warning, since it’s still reproduces with a number in my example. I believe the area of the problem is somewhere in initialization and first updating of observable |
Beta Was this translation helpful? Give feedback.
-
So Michel had a look at it and the conclusion is that it seems like a false negative for that warning. There is technically nothing wrong with the pattern I've outlined in previous sandbox. If we would go through modifying the If the warning bothers you, it would be better to avoid MobX in this case because there doesn't seem to be an added value for you. Try to rethink the approach. Basically what @nulladdict said applies here. Consume The golden rule is that you should pick the right tool for the job, not to force a single tool for every job. |
Beta Was this translation helpful? Give feedback.
-
Yeah, it is clear to me how the warning is caused :) It just makes me
wonder, is asAsObservableSource the right solution, or do we need a
different abstraction (e.g. a hoc, wrapper, dunno). That is why I'm
interested in more real life use cases like the router one, rather than
minimal repo's (which in turn are better to verify the solution). The risk
with any useEffect based solution is that it might easily cause double
renders. That in turn is probably fixable, just want to zoom out a bit
first and make sure, before we start piling fixes / workarounds, that we're
working in the right direction.
…On Thu, Aug 6, 2020 at 10:04 AM Björn Zeutzheim ***@***.***> wrote:
@mweststrate <https://github.com/mweststrate> as for a mobx-react-lite
related example you can check my comment at the top
<https://github.com/mobxjs/mobx-react-lite/issues/274#issuecomment-652958242>
and this comment
<https://github.com/mobxjs/mobx-react-lite/issues/274#issuecomment-653524231>
for a general demonstration of where this problem originates from.
To sum it up, react now says that calling setState of a different
component from within the rendering of one component is regarded as an
error.
It still works right now, but I guess they did this because it will fail
in the future with react concurrent mode.
Right now this error will always occurr when the observable from
useAsObservableSource is used in any other component than the current.
This is because inside the hook it will update the observable object and
if any other component depends on one of those values, it will be
forceUpdated via setState while still rendering the current component.
—
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/274#issuecomment-669807151>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBFFZT5F4HO7T2XARMDR7JWYPANCNFSM4MD5D7QQ>
.
|
Beta Was this translation helpful? Give feedback.
-
@mweststrate There is a double render already, currently a |
Beta Was this translation helpful? Give feedback.
-
Yeah, the biggest problem is here indeed that unlike with classes, we don't have a
or make the props directly observable directly anyway in the HoC. There was a problem with that, but I don't remember anymore what exactly :) I'm going on holiday tomorrow, but I see if I can recall it after that. |
Beta Was this translation helpful? Give feedback.
-
Not sure if it helps anything, because it's the local store that is passable around and contains computeds that depends on this source ( To be completely clear I don't like Enjoy your holiday :) |
Beta Was this translation helpful? Give feedback.
-
I think deferring the update into a useEffect will not have any impact, because afaik right now react internally does the same thing this patch does manually.
There might be an alternative just for useAsObservableSource. Something like this maybe? import React from 'react';
import { observable } from 'mobx';
export interface WithObservablePropsProps<T extends Record<string, any>> {
observableProps: T;
}
export default function withObservableProps<T extends Record<string, any>>(Component: React.ComponentType<WithObservablePropsProps<T>>) {
const result: React.ComponentType<T> = function WithObservableProps(props: T) {
const observableProps = React.useState(() => observable(props))[0];
React.useEffect(() => {
Object.assign(observableProps, props);
});
return <Component observableProps={observableProps} />;
};
return result;
} And then using it like this: const MyComponent = observer(({ observableProps }: { observableProps: { num: number; str: string; }; }) {
return (
<div>{observableProps.num} / {observableProps.str}</div>
);
});
const ComponentWithObservableProps = withObservableProps(MyComponent);
/*
type of ComponentWithObservableProps is
const ComponentWithObservableProps: React.FunctionComponent<{
num: number;
str: string;
}>
*/ However there are a few cons with this solution:
|
Beta Was this translation helpful? Give feedback.
-
I would assume that react schedules setState immediately (at least for current component), while effects wait for the DOM, but really dunno.
We would have to move Another thing I am a bit worried: the Deleted the comment above, realized it can't work :D |
Beta Was this translation helpful? Give feedback.
-
For the current one yes, but we are talking about other components - in this case from what I can infer, react currently schedules them to render after the current one.
I think this might be possible, but I think it would only happen with code which would cause the same problem even with the current implementation (ie. modifying an observable value in a component during rendering which at the same time relies on this value): const store = observable({ value: 0 });
const FailComponent = observer(() => {
store.value += 1;
return <div>Value = {store.value}</div>;
});
As I said in my comment above - that this was just an example and this solution would have quite a few cons - so I also definitely would never use this solution.
Would not be a problem, because the new values would be applied in a useEffect which would keep the changes still in sync in regards to the whole rendering process. So to get back on topic: Are there so far any arguments against going forward with solution proposed in the PR mobxjs/mobx-react-lite#299 ? |
Beta Was this translation helpful? Give feedback.
-
It may break some assumptions in the render function ... what usually happens (to me) is
My main argument is againts |
Beta Was this translation helpful? Give feedback.
-
As I said before, that should not be possible.
I improved the PR to separate the delaying logic of force-update calls into a separate hook which can then be used in those places.
I think react already does this so this might not be necessary anymore.
I don't really understand your example - if it is still unclear for you, can you try to explain it again in other words? |
Beta Was this translation helpful? Give feedback.
-
Just a random thought I had, that might be relevant to the overall discussion. To illustrate, instead of this: const Component = observer(props => {
const state = useLocalStore(source => ({ foo: 1 + source.bar }), props)
/* ... */
}) We can encourage something like this: const Component = observer(props => {
const state = useLocalStore(() => ({ foo: 1 }))
const foo = state.foo + props.bar
/* ... */
}) This way whatever uses non-observable state will be forced to compute synchronously right inside render, and we also might keep observable updates synchronous, unless there's outright mutation happening in render. |
Beta Was this translation helpful? Give feedback.
-
I was referring to that HOC solution, where the
Yea but it's not really obvious whats wrong, I quess we should at least mention it in docs.
Gave it some more thoughts, it's probably fine. |
Beta Was this translation helpful? Give feedback.
-
@urugator So afaik there are no more open questions. |
Beta Was this translation helpful? Give feedback.
-
Fixed in 2.1.0 |
Beta Was this translation helpful? Give feedback.
-
I still experienced this bug using nextjs and mobx-react-lite 2.2.0, maybe I did something wrong ? |
Beta Was this translation helpful? Give feedback.
-
@rizkyramadhan Please open a new issue with all details and ideally minimal reproduction. |
Beta Was this translation helpful? Give feedback.
-
✋✋✋ The original issue below doesn't represent true reasons. The important discussion starts at #2539
Intended outcome
Not trigger warning.
Actual outcome
warning from react:
How to reproduce the issue
Try to run this code
Beta Was this translation helpful? Give feedback.
All reactions