feat: add `useCoValueRef` and similar hooks
I've been using my own version of this useRef pattern for a while now, and it has made a huge difference in the performance of our app.
In the updated music player example, you can see how much less data is being selected now.
Quick summary:
- Added the new hooks
- Refactored all the hooks to share as much code and types as possible
- Add
useReftocreateCoValueSubscriptionContextandcreateAccountSubscriptionContext - Added the
passthroughNotLoadedoption for the providers to skip the fallback rendering logic. I found this is useful for cases where we want to load at the top level of our app something like the first or last value of a list, but still be able to handle empty lists. - I've made
useCoStateAndRefanduseAccountAndRefreturn the ref with an extra $isLoaded assigned that can narrow the type of the ref. This is useful when wanting to pass the refs between components and not forcing the child components keep checkingif (xRef.current.$isLoaded) ...
[!NOTE] Introduce ref-based hooks for non-reactive access to CoValues/accounts, add
useRefto subscription contexts, update exports, and refactor the music-player example and tests to use the new APIs.
- API (react-core/react/react-native)
- Add hooks:
useCoValueRef,useAccountRef,useCoStateAndRef,useAccountAndRef, and internaluseSubscriptionRef.- Expose
useReffromcreateCoValueSubscriptionContextandcreateAccountSubscriptionContext.- Refine selector utilities:
useSubscriptionSelectorupdates;identitySelectorhelper.- Export new types
CoValueRef,MaybeLoadedCoValueRefand wire throughreactandreact-nativepackages.- Minor: add
CoValueLoadingStatenamespace types; remove unused context util.- Examples (music-player)
- Replace
useCoStateusages withuseCoStateAndRefand contextuseAccountRefto edit via refs without re-renders.- Update components (
HomePage,EditPlaylistModal,MemberAccessModal,MusicTrackRow,MusicTrackTitleInput,PlaylistTitleInput,WelcomeScreen,SidePanel,AuthModal,PlayerControls,Waveform) to use selectors and ids; pass refs instead of full objects.- Add
shallowEqualhelper inlib/utilsfor selector equality.AccountProvidernow exportsuseAccountRef.- Tests
- Add comprehensive tests for new hooks and contexts (
useCoValueRef,useAccountRef,useCoStateAndRef,useAccountAndRef, subscription contexts, selector behavior); adduseRenderCounttest util.- Changeset
- Patch release for
jazz-toolsdescribing new hooks and provideruseRef.Written by Cursor Bugbot for commit ecf74832807b733b99e47025c0ed88d9176899fa. This will update automatically on new commits. Configure here.
@Gabrola is attempting to deploy a commit to the Garden Computing Team on Vercel.
A member of the Team first needs to authorize it.
@gdorsi Yeah, I was on the fence with this regarding how it might be misused where some users might use it in rendered code.
I believe useCoValueSubscription and useAccountSubscription are already exported; we are indeed using them and have the same ref-based hooks internally.
I think the biggest current limitation without the ref hooks, is that there is no easy way to access the $jazz API for imperative logic without quite a bit of code in the select and equalityFn functions. The first idea is to narrow down the scope of the use-Ref hooks to be instead useCoValueJazzApi() and still expose it through a ref, but still there is a risk of it being used in rendered code for things like $jazz.id or $jazz.owner.
I think a really good alternative solution would be to make the $jazz API referentially stable such that it does not cause re-renders when selected in useCoState. I looked into it and unfortunately it didn't seem like this was simple to achieve.
Either way, for this PR I can change the scope, and remove the ref-based hooks and keep the rest of the changes, mainly the refactor and the createCoValueSubscriptionContext and createAccountSubscriptionContext changes (without useRef). I can also expose the Context object, this will allow us to build similar functionality by accessing the subscription object.