mobxjs / mobx

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

forceUpdate bug on observer components: "Warning: Cannot update a component (`X`) while rendering a different component (`X`)" #3752

Open hasanayan opened 1 year ago

hasanayan commented 1 year ago

Intended outcome: observer components should not force a re-render during a render

Actual outcome: observer components are forcing re-render during a render

How to reproduce the issue: I'm trying to integrate react-query and mobx using fromResource. Everything works fine except, when 2 different components consuming the same resource are mounted, the second once logs a warning (bad setState call warning). It is hard to explain throughly here but I created a public repo that showcases this pretty simply.

clone this repo: https://github.com/hasanayan/mobx-react-query

Versions

"mobx": "^6.10.2", "mobx-react": "^9.0.1", "mobx-utils": "^6.0.8", "@tanstack/react-query": "5.0.0-beta.20" (I believe this is irrelevant)

urugator commented 1 year ago

I think it's because repository.getEntity(3) is called during render and contains a side effect. That alone is problemantic - subscribing to resources etc should be done done in useEffect. It doesn't warn in the first case, because the side effect is async, but in the second case, the data are resolved immediately, so the sink is called during render. fromResouce doesn't care whether the value actually changed or not, so any time you call sink, it translates to setState on every component that is observing the resource.

rvion commented 1 day ago

IMO, this problems is indeed problematic and would really need a proper solution documented somewhere / or even a small fix or primitive mobx-side;

in my experience, it inevitably comes up in larger codebases, or in codebase using nice abstractions like repositories to access/cache/initialize entities on the frontend.

Even when the some entity map initialization is properly done within a reactAction, the action still finishes while the second compoenent is still rendering, and that trigger the first component re-render. I'm fairly good with mobx / know internals a bit, but still can't figure out the best way to deal with that.

if someone has a principled solution, I'd really love to hear it.

hasanayan commented 1 day ago

I agree, @rvion. I am no longer working on that project (which was fairly big one) but we "solved" it by calling sink in a setTimeout (you can use 0 as timeout) so that the execution of sink call is done after the render. I don't think the execution order is critical in that case and we did not face any problems with that approach