The demo below renders 3 dots, each of which include render blocking code to simulate expensive renders, and a button to toggle the colors of the dots. The idea is to attempt a state tearing update in between the first and second dot renders. If the tearing attempt worked, the first dot's final color will not match the color of the second two dots.
It's important to note that state tearing will only occur in one of the scenarios, but I included several other global state strategies to show how their solutions introduce new tradeoffs. For example, React's new useSyncExternalStore does not support the transition API and falls back to synchronous rendering if inconsistencies are detected. This is visible in the demo because the button never turns grey after the user clicks it.
render mode: concurrent - strategy: external store tracked via `useEffect` and `useState`
This works in sync mode but will cause tearing in concurrent mode. Because it supports the transition API, the tearing attempt will occur mid-render instead of waiting for the the dot renders to complete.
render mode: concurrent - strategy: external store tracked via useSyncExternalStore
This is the new way of managing external global state via `useSyncExternalStore` and will NOT cause tearing. The tradeoff is that `useSyncExternalStore` will fallback to rendering in sync mode if it detects inconsistencies. It also will not work with React's new transition APIs. This is easy to see because the button never turns grey after the user clicks it.
render mode: concurrent - strategy: store managed withing React's context API
This uses React's context API to manage state and will NOT cause tearing. It also works with React's new transition APIs. The reason why this approach is not more popular, though, is because React's `useContext` does not allow fine-grained reactivity and will cause unnecessary rerenders in components that only need to access a slice of the store.
render mode: sync - strategy: external store tracked via `useEffect` and `useState`
This works because it does not make use of the transition API and will wait for the dot renders to complete before making the tearing attempt.
render mode: sync - strategy: external store tracked via useSyncExternalStore
This works in synchronous mode but isn't necessary since synchronous renders should result in the consistent state.
render mode: sync - strategy: store managed withing React's context API
This uses React's context API to manage global state and will NOT cause tearing. In sync mode (and probably in concurrent mode too), this is never a recommended way of managing global state due to rerender concerns.
Important: the lifecycle events below the dots section will tell you exactly what is happening with the render lifecyle under the hood.
Current strategy: external store tracked via `useEffect` and `useState`
Will tear: true
Supports React transition API: true
This works in sync mode but will cause tearing in concurrent mode. Because it supports the transition API, the tearing attempt will occur mid-render instead of waiting for the the dot renders to complete.