-
Notifications
You must be signed in to change notification settings - Fork 47k
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
RFClarification: why is setState
asynchronous?
#11527
Comments
We're all waiting @gaearon . |
@Kaybarax Hey, it's weekend ;-) |
@mweststrate Oh! my bad. Cool. |
I'm gonna go out on a limb here and say it's because of batching multiple |
I'm going on vacation next week but I'll probably go on Tuesday so I'll try to reply on Monday. |
function enqueueUpdate(component) { // Various parts of our code (such as ReactCompositeComponent's if (!batchingStrategy.isBatchingUpdates) { dirtyComponents.push(component); |
@mweststrate just 2 cents: that is very valid question. |
@mweststrate interestingly I asked the same question here: https://discuss.reactjs.org/t/historic-reasons-behind-setstate-not-being-immediately-visible/8487 |
I've personally had and seen in other developers confusion on this subject. @gaearon it'd be great to get an explanation for this when you have some time :) |
Sorry, it's the end of the year and we've been a bit behind on GitHub etc trying to wrap up everything we've been working on before the holidays. I do intend to come back to this thread and discuss it. But it's also a bit of a moving target because we're currently working on async React features that directly relate to how and when |
So here’s a few thoughts. This is not a complete response by any means, but maybe this is still more helpful than saying nothing. First, I think we agree that delaying reconciliation in order to batch updates is beneficial. That is, we agree that For example, if we’re inside a browser You’re asking: why can’t we do the same exact thing (batching) but write Guaranteeing Internal ConsistencyEven if Right now the objects provided by React ( When you use just the state, if it flushed synchronously (as you proposed), this pattern would work: console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2 However, say this state needs to be lifted to be shared across a few components so you move it to a parent: -this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // Does the same thing in a parent I want to highlight that in typical React apps that rely on However, this breaks our code! console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0 This is because, in the model you proposed, There are also more subtle cases of how this can break, e.g. if you’re mixing data from These examples are not at all theoretical. In fact React Redux bindings used to have exactly this kind of problem because they mix React props with non-React state: reduxjs/react-redux#86, reduxjs/react-redux#99, reduxjs/react-redux#292, reduxjs/redux#1415, reduxjs/react-redux#525. I don’t know why MobX users haven’t bumped into this, but my intuition is that they might be bumping into such scenarios but consider them their own fault. Or maybe they don’t read as much from So how does React solve this today? In React, both Yes, this can be inconvenient in some cases. Especially for folks coming from more OO backgrounds who just want to mutate state several times instead of thinking how to represent a complete state update in a single place. I can empathize with that, although I do think that keeping state updates concentrated is clearer from a debugging perspective: #122 (comment). Still, you have the option of moving the state that you want to read immediately into some sideways mutable object, especially if you don’t use it as a source of truth for rendering. Which is pretty much what MobX lets you do 🙂. You also have an option to flush the entire tree if you know what you’re doing. The API is called To sum up, the React model doesn’t always lead to the most concise code, but it is internally consistent and ensures lifting state up is safe. Enabling Concurrent UpdatesConceptually, React behaves as if it had a single update queue per component. This is why the discussion makes sense at all: we discuss whether to apply updates to Recently, we’ve been talking about “async rendering” a lot. I admit we haven’t done a very good job at communicating what that means, but that’s the nature of R&D: you go after an idea that seems conceptually promising, but you really understand its implications only after having spent enough time with it. One way we’ve been explaining “async rendering” is that React could assign different priorities to For example, if you are typing a message, If we let certain updates have “lower priority”, we could split their rendering into small chunks of a few milliseconds so they wouldn’t be noticeable to the user. I know performance optimizations like this might not sound very exciting or convincing. You could say: “we don’t need this with MobX, our update tracking is fast enough to just avoid re-renders”. I don’t think it’s true in all cases (e.g. no matter how fast MobX is, you still have to create DOM nodes and do the rendering for newly mounted views). Still, if it were true, and if you consciously decided that you’re okay with always wrapping objects into a specific JavaScript library that tracks reads and writes, maybe you don’t benefit from these optimizations as much. But asynchronous rendering is not just about performance optimizations. We think it is a fundamental shift in what the React component model can do. For example, consider the case where you’re navigating from one screen to another. Typically you’d show a spinner while the new screen is rendering. However, if the navigation is fast enough (within a second or so), flashing and immediately hiding a spinner causes a degraded user experience. Worse, if you have multiple levels of components with different async dependencies (data, code, images), you end up with a cascade of spinners that briefly flash one by one. This is both visually unpleasant and makes your app slower in practice because of all the DOM reflows. It is also the source of much boilerplate code. Wouldn’t it be nice if when you do a simple It turns out that, with current React model and some adjustments to lifecycles, we actually can implement this! @acdlite has been working on this feature for the past few weeks, and will post an RFC for it soon. Note that this is only possible because I don’t want to steal the thunder from @acdlite with regards to announcing all of this but I hope this does sound at least a bit exciting. I understand this still might sound like vaporware, or like we don’t really know what we’re doing. I hope we can convince you otherwise in the coming months, and that you’ll appreciate the flexibility of the React model. And as far as I understand, at least in part this flexibility is possible thanks to not flushing state updates immediately. |
Wonderful in depth explanation to the decisions behind the architecture of React. Thanks. |
mark |
Thank you, Dan. |
I ❤️ this issue. Awesome question and awesome answer. I always thought that this was a bad design decision, now I have to rethink 😄 |
Thank you, Dan. |
I call it asyncAwesome setState 😄 |
I tend to think that everything should be implemented async first, and if you find a need for a sync operation, well, wrap the async operation with a wait for completion. It's much easier to make sync code out of async code (all you need is a wrapper) than the reverse (which basically requires a complete rewrite, unless you reach for threading, which is not at all light-weight). |
@gaearon thanks for the extensive explanation! It has been nagging me for a long time ("there must be a good reason, but nobody can tell which one"). But now it makes total sense and I see how this is a really conscious decision :). Thanks a lot for the extensive answer, really appreciate it!
I think this is quite true indeed, in MobX props are typically used as just component configuration, and the domain data is typically not captured in props, but in domain entities that are passed around between components. Again, thanks a lot! |
@gaearon Thanks for the detailed and great explanation. When the event is registered "Outside React", that means maybe through
When we will click on the button "React Event", we will see in the console: After some small research and browsing through the source code, i think i know why this happens. What are your thoughts about this? 🤔 |
This comment has been minimized.
This comment has been minimized.
thank u dan |
mark |
thanks dan |
mark react async render |
where should I find the |
@mweststrate @gaearon Should we call setState is asynchronous? As I know, react render works asynchronously, but setState function is not async itself. |
setState is not a async function, but it's kinda "asynchronious" since the effect is delayed. Async programming is way older than when javascript got the |
For quite a while I've tried to understood why
setState
is asynchronous. And failing to find an answer to it in the past, I came to the conclusion that it was for historical reasons and probably hard to change now. However @gaearon indicated there is a clear reason, so I am curious to find out :)Anyway, here are the reasons I often hear, but I think they can't be everything as they are too easy to counter
Async setState is required for async rendering
Many initially think it is because of render efficiency. But I don't think that is the reason behind this behavior, because keeping setState sync with async rendering sounds trivial to me, something along the lines of:
In fact, for example
mobx-react
allows synchronous assignments to observables and still respect the async nature of renderingAsync setState is needed to know which state was rendered
The other argument I hear sometimes is that you want to reason about the state that was rendered, not the state that was requested. But I'm not sure this principle has much merit either. Conceptually it feels strange to me. Rendering is a side effect, state is about facts. Today, I am 32 years old, and next year I will turn 33, regardless whether the owning component manages to re-render this year or not :).
To draw a (probably not to good) parallel: If you wouldn't be able to read your last version of a self written word document until you printed it, that would be pretty awkward. I don't think for example game engines give you feedback on what state of the game was exactly rendered and which frames were dropped either.
An interesting observations: In 2 years
mobx-react
nobody ever asked me the question: How do I know my observables are rendered? This question just seems not relevant very often.I did encounter a few cases where knowing which data was rendered was relevant. The case I remember was where I needed to know the pixel dimensions of some data for layout purposes. But that was elegantly solved by using
didComponentUpdate
and didn't really rely onsetState
being async either. These cases seem so rare that it hardly justify to design the api primarily around them. If it can be done somehow, it suffices I thinkI have no doubt that the React team is aware of the confusion the async nature of
setState
often introduces, so I suspect there is another very good reason for the current semantics. Tell me more :)The text was updated successfully, but these errors were encountered: