Test tearing and branching in React concurrent rendering
Caveat: These tests are originally designed for obsolete concurrent mode. We should revisit them for new concurrent rendering feature.
React 18 is coming with a new feature called "concurrent rendering". With global state, there's a theoretical issue called "tearing" that might occur in React concurrent rendering.
Let's test the behavior!
- What is tearing in React 18 WG
- Stack Overflow
- Talk by Mark Erikson
- Talk by Flarnie Marchan
- Some other resources
- Old resources
A small app is implemented with each library. The state has one count. The app shows the count in fifty components.
There's a button outside of React and if it's clicked it will trigger state mutation. This is to emulate mutating an external state outside of React, for example updating state by Redux middleware.
The render has intentionaly expensive computation. If the mutation happens during rendering with in a tree, there could be an inconsistency in the state. If it finds the inconsistency, the test will fail.
git clone https://github.com/dai-shi/will-this-react-global-state-work-in-concurrent-rendering.git
cd will-this-react-global-state-work-in-concurrent-rendering
yarn install
yarn run build-all
yarn run jest
To automatically run tests and update the README.md on OSX:
yarn jest:json
yarn jest:update
- Level 1
- test 1: updated properly with transition
- Level 2
- test 2: no tearing with transition
- test 5: updated properly with auto increment (EXPERIMENTAL)
- test 6: no tearing with auto increment (EXPERIMENTAL)
- Level 3
- test 3: ability to interrupt render
- test 4: proper branching with transition
Raw Output
react-redux
with useTransition
✓ test 1: updated properly with transition (3953 ms)
✓ test 2: no tearing with transition (24 ms)
✕ test 3: ability to interrupt render (1 ms)
✕ test 4: proper branching with transition (4512 ms)
with intensive auto increment
✓ test 5: updated properly with auto increment (2210 ms)
✕ test 6: no tearing with auto increment (2 ms)
react-tracked
with useTransition
✓ test 1: updated properly with transition (3649 ms)
✓ test 2: no tearing with transition (27 ms)
✓ test 3: ability to interrupt render
✓ test 4: proper branching with transition (5472 ms)
with intensive auto increment
✓ test 5: updated properly with auto increment (6114 ms)
✓ test 6: no tearing with auto increment
constate
with useTransition
✓ test 1: updated properly with transition (2652 ms)
✓ test 2: no tearing with transition (25 ms)
✓ test 3: ability to interrupt render
✓ test 4: proper branching with transition (3430 ms)
with intensive auto increment
✓ test 5: updated properly with auto increment (4003 ms)
✓ test 6: no tearing with auto increment (1 ms)
zustand
with useTransition
✓ test 1: updated properly with transition (3938 ms)
✓ test 2: no tearing with transition (27 ms)
✕ test 3: ability to interrupt render (1 ms)
✕ test 4: proper branching with transition (4520 ms)
with intensive auto increment
✓ test 5: updated properly with auto increment (2202 ms)
✕ test 6: no tearing with auto increment (2 ms)
react-hooks-global-state
with useTransition
✓ test 1: updated properly with transition (3526 ms)
✓ test 2: no tearing with transition (25 ms)
✓ test 3: ability to interrupt render
✕ test 4: proper branching with transition (7247 ms)
with intensive auto increment
✕ test 5: updated properly with auto increment (13203 ms)
✕ test 6: no tearing with auto increment (4 ms)
use-context-selector
with useTransition
✓ test 1: updated properly with transition (3677 ms)
✓ test 2: no tearing with transition (26 ms)
✓ test 3: ability to interrupt render
✓ test 4: proper branching with transition (5460 ms)
with intensive auto increment
✓ test 5: updated properly with auto increment (6138 ms)
✓ test 6: no tearing with auto increment (1 ms)
use-subscription
with useTransition
✓ test 1: updated properly with transition (3560 ms)
✓ test 2: no tearing with transition (120 ms)
✓ test 3: ability to interrupt render
✕ test 4: proper branching with transition (7571 ms)
with intensive auto increment
✕ test 5: updated properly with auto increment (13202 ms)
✕ test 6: no tearing with auto increment (9 ms)
react-state
with useTransition
✓ test 1: updated properly with transition (2643 ms)
✓ test 2: no tearing with transition (27 ms)
✓ test 3: ability to interrupt render
✓ test 4: proper branching with transition (3453 ms)
with intensive auto increment
✓ test 5: updated properly with auto increment (4021 ms)
✓ test 6: no tearing with auto increment (4 ms)
simplux
with useTransition
✓ test 1: updated properly with transition (2671 ms)
✓ test 2: no tearing with transition (28 ms)
✓ test 3: ability to interrupt render
✕ test 4: proper branching with transition (7411 ms)
with intensive auto increment
✓ test 5: updated properly with auto increment (4102 ms)
✓ test 6: no tearing with auto increment (2 ms)
apollo-client
with useTransition
✓ test 1: updated properly with transition (3645 ms)
✕ test 2: no tearing with transition (31 ms)
✕ test 3: ability to interrupt render (1 ms)
✕ test 4: proper branching with transition (3626 ms)
with intensive auto increment
✓ test 5: updated properly with auto increment (2260 ms)
✕ test 6: no tearing with auto increment (1 ms)
recoil
with useTransition
✓ test 1: updated properly with transition (3626 ms)
✓ test 2: no tearing with transition (26 ms)
✕ test 3: ability to interrupt render
✕ test 4: proper branching with transition (4375 ms)
with intensive auto increment
✓ test 5: updated properly with auto increment (3035 ms)
✓ test 6: no tearing with auto increment (1 ms)
jotai
with useTransition
✓ test 1: updated properly with transition (3682 ms)
✓ test 2: no tearing with transition (24 ms)
✓ test 3: ability to interrupt render
✕ test 4: proper branching with transition (8401 ms)
with intensive auto increment
✓ test 5: updated properly with auto increment (5206 ms)
✓ test 6: no tearing with auto increment (1 ms)
effector
with useTransition
✓ test 1: updated properly with transition (2492 ms)
✕ test 2: no tearing with transition (25 ms)
✓ test 3: ability to interrupt render
✕ test 4: proper branching with transition (981 ms)
with intensive auto increment
✓ test 5: updated properly with auto increment (2221 ms)
✕ test 6: no tearing with auto increment (1 ms)
react-rxjs
with useTransition
✓ test 1: updated properly with transition (3933 ms)
✓ test 2: no tearing with transition (33 ms)
✕ test 3: ability to interrupt render (1 ms)
✕ test 4: proper branching with transition (4532 ms)
with intensive auto increment
✓ test 5: updated properly with auto increment (3008 ms)
✓ test 6: no tearing with auto increment
valtio
with useTransition
✓ test 1: updated properly with transition (3533 ms)
✓ test 2: no tearing with transition (24 ms)
✓ test 3: ability to interrupt render
✕ test 4: proper branching with transition (7250 ms)
with intensive auto increment
✕ test 5: updated properly with auto increment (13210 ms)
✕ test 6: no tearing with auto increment (3 ms)
Test | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
react-redux | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
react-tracked | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
constate | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
zustand | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
react-hooks-global-state | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
use-context-selector (w/ useReducer) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
use-subscription (w/ redux) | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
simplux | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
apollo-client | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
recoil | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
jotai | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
effector | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
react-rxjs | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
valtio | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
- Tearing and state branching may not be an issue depending on app requirements.
- The test is done in a very limited way.
- Passing tests don't guarantee anything.
- The results may not be accurate.
- Do not fully trust the results.
The reason why I created this is to test my projects.
- react-tracked
- use-context-selector
- and so on
This repository is a tool for us to test some of global state libraries. While it is totally fine to use the tool for other libraries under the license, we don't generally accept adding a new library to the repository.
However, we are interested in various approaches. If you have any suggestions feel free to open issues or pull requests. We may consider adding (and removing) libraries. Questions and discussions are also welcome in issues.
For listing global state libraries, we have another repository https://github.com/dai-shi/lets-compare-global-state-with-react-hooks in which we accept contributions. It's recommended to run this tool and we put the result there, possibly a reference link to a PR in this repository or a fork of this repository.