What is tearing? #69
Replies: 3 comments 22 replies
-
👍 Thanks for the nice explanation, Rick. (I added a link from my repo.) |
Beta Was this translation helpful? Give feedback.
-
Could also go in #70 but I feel like "What is tearing" is currently missing an example of a teared UI that can already be observed using just hooks in React 17. But maybe this isn't tearing? I see a particular pattern with The code I'm going to describe already tears in React17 and can be rewritten to avoid tearing (and make it more reasonable in general IMO). I only want to point this out since it seems to me that you're creating a false sense of security by saying "only other libraries can cause tearing". Though the tearing already happens without concurrent rendering anyway. function Input({ disabled }) {
const [focused, setFocused] = React.useState(false);
React.useEffect(() => {
if (disabled && focused) {
setFocused(false);
}
}, [disabled]);
// focus handler
// markup goes here
return ...
} The tearing arises when the Now this example is fairly obvious. Without going into a discussion what the correct model would be or if you need to use A similar faulty implementation can be applied to your tree illustration: function Tree({ color }) {
const [items, setItems] = React.useState([{ color, selected: false }, { color, selected: false }]);
React.useEffect(() => {
setItems([{ color, selected: false }, { color, selected: false }])
}, [color])
return (
<div>
<p>Data is {color}</p>
<Items items={items} />
</div>
);
} When changing the color, one paint would contain "Data is red" with all items being blue (inconsistent). The next paint would contain "Data is blue" with all items being blue (consistent). I suspect that the reason for using Concrete examples (ordered by how obscure the "derived state" pattern is, starting with less obscure):
|
Beta Was this translation helpful? Give feedback.
-
Great explanation! I have some questions related to the terms mentioned above expecting more clarification. Q1 - external stateIf user sync state from props in class components to class properties, will thoes properties be considered as a external state? Q2 - React Yield BehaviorFor class component lifecycles, which ones would be also discard when react yield to something else during rendering?
When react try to dedupe some render results and update with the latest one, |
Beta Was this translation helpful? Give feedback.
-
Overview
Tearing is a term traditionally used in graphics programming to refer to a visual inconsistency.
For example, in a video, screen tearing is when you see multiple frames in a single screen, which makes the video look “glitchy”. In a user interface, by “tearing” we mean that a UI has shown multiple values for the same state. For example, you may show different prices for the same item in a list, or you submit a form for the wrong price, or even crash when accessing outdated store values.
Since JavaScript is single threaded, this issue generally has not come up in web development. But in React 18, concurrent rendering makes this issue possible because React yields during rendering. This means that when using a concurrent feature like
startTransition
orSuspense
, React can pause to let other work happen. Between these pauses, updates can sneak in that change the data being used to render, which can cause the UI to show two different values for the same data.This problem isn’t specific to React, it’s a necessary consequence of concurrency. If you want to be able to interrupt rendering to respond to user input for more responsive experiences, you need to be resilient to the data you’re rendering changing and causing the user interface to tear.
This problem was explained in a talk by @flarnie here, but we'll provide a brief overview of the problem here by explaining what happens during synchronous rendering, and concurrent rendering.
Synchronous rendering
Take a look at the figure below.
In the first panel, we begin rendering the React tree. We get to a component that needs to access some external store and get a color value. The external store says that the color is blue, so that component renders blue.
In the second panel, since we’re not concurrently rendering, React continues rendering all of the components without stopping. Since we didn’t stop (or “yield”), then the external store could not have changed. So all of the components get the same value in the external store.
In the third panel, we see that all of the components are rendered as blue, and they all look the same. The UI is always displayed in a consistent state, because everything you see is rendered with the same value everywhere on screen.
Finally, in the fourth panel the store is able to update. This is because React finished, and allowed other work to happen. If the store updates when React isn’t rendering, then the next time React renders the tree, we’ll start over from the first panel, and all of the components will get the same value.
This is why, before concurrent rendering, and in most other UI frameworks, the UI is always rendered consistently. This is the way React works through React 17 and by default in React 18 when you do not use a concurrent feature.
Concurrent rendering
Most of the time, concurrent rendering results in a consistent UI but there is an edge case that can cause issues under the right conditions. To see what can happen, see the next figure.
We start the first panel the same as before, and render the component blue.
Here’s where we can start to differ.
Since we’re using a concurrent feature, we’re using concurrent rendering and React can stop working before it’s done, “yielding” to other work. This is a huge benefit for responsiveness because the user is able to interact with the page without React blocking them. In this case, say a user clicks a button that changes the store from blue to red. This wouldn’t have even been possible to handle before concurrent rendering. The page would just seem paused to the user, and they couldn’t click anything. But with concurrent rendering React can let the click happen so the page feels fluid and interactive to the user.
A consequence of this feature is that user interactions (or other work like network requests or timeouts) can change the values in the external state that are being used to render what you see on screen. This is the edge case that can cause issues. In the second panel, we see that React has yielded and the external store has changed based on the user interaction.
The issue it that first component has already rendered blue (because that was the value of the store at that time), but any component that renders after this will get the current value, which is now red. In the third panel, that’s exactly what happens. Now, the components that access external state render and get the current value, which is red.
Finally, in the last panel we can see that the component which were previously always consistently blue are now a mix of red and blue. They’re displaying two different values for the same data. This edge case is “tearing”.
Related posts
Beta Was this translation helpful? Give feedback.
All reactions