Dear reader,
In the last few years, I've been using my own global store implementation with great success (yes, I'm aware there exist a bazillion "redux like" stores). My one's called tiny-atom.
In particular, the following 4 requirements for a global store emerged from my needs:
For example, tiny-atom uses requestAnimationFrame
to delay/batch the re-renders.
If naively implemented – a child component re-renders once from parent re-render, once from it's own subscription.
For example, a child component can break reading something non existant from store, when parent was supposed to un-render that child.
Important for performance, when many components bind to small slices of the shared state.
My current implementation is not Concurrent Mode ready. So, excitedly, I've tried out the new useMutableSource
in [email protected]
in place
of my current approach. It works pretty great! The useMutableSource
hook takes care of requirements 1-3 out of the box only leaving requirement 4 to the implementer!
Right now, I'm looking for the following answers:
How to correctly prevent re-rendering after store change if new snapshot is shallowly equal?
You can see my current approach below, but I don't think it's correct wrt Concurrent Mode.
UPDATE: I have updated the gist and code sandbox with a correct (?) implementation of selective subscription now. No more mutating refs.
Is this type of implementation fully Concurrent Mode compatible?
I tried running this simple store implementation through the https://github.com/dai-shi/will-this-react-global-state-work-in-concurrent-mode test suite and the following checks fail:
✕ check 7: proper branching with transition
✕ check 9: no tearing with auto increment
My concern is - does that mean useMutableSource
still has some tearing issues in some cases? (Note: I haven't looked into the the implementation of the test suite closely).
UPDATE Anyone know how to convert this super simple store into a fully React compliant store? E.g. I don't mind if it's turned into a useState/useReducer as long as it can be used in a similar manner?
I've put the code used in my exercise here: https://codesandbox.io/s/flamboyant-keller-m9unj
App.js
- a demo app for exploring the 4 requirements abovecreateStoreModern.js
- simpler store used inuseMutableSource
implementaionuseStoreModern.js
- implementation of store hooks usinguseMutableSource
createStoreLegacy.js
- more complex store that allows order specified subscriptionsuseStoreLegacy.js
- implementation of hooks withoutuseMutableSource
I'm also including the code for the modern implementation directly below
getSnapshot must return an immutable object. I suspect what you return can be an object that is mutated later.
If you return an immutable object, then you would get the benefit of no re-rendering.
If your getSnapshot returns an immutable object, but you still want to avoid re-renders if it's shallowly equal, you would need scoped subscriptions.
Check out this comment and the snippet linked in the comment. (It's obsolete though, because in my case I don't need shallow equal checks.)
Check 7 is not possible to pass with an external store. So, that's expected. Note, that doesn't mean it's CM-unsafe. It just means it can't get benefits from CM. (I need to add some notes there, but then I also need to explain state branching...)
Check 9 is an intentional test to distinguish useSubscription and useMutableSource. It's expected to pass with useMutableSource, but it's failing too on my end with
0.0.0-experimental-8b155d261
.General notes about the tool: As it tries to reproduce and test tearing (except for check 7) from outside React (except for check 9), it's very tricky (= adding long wait in render). These tests are not 100% accurate. There can be other cases for tearing, and there can be other issues to be CM-safe. Specifically, check 7 is not about CM-safe, but about benefits from CM.