jazz icon indicating copy to clipboard operation
jazz copied to clipboard

feat: add `useCoValueRef` and similar hooks

Open Gabrola opened this issue 5 months ago • 2 comments

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 useRef to createCoValueSubscriptionContext and createAccountSubscriptionContext
  • Added the passthroughNotLoaded option 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 useCoStateAndRef and useAccountAndRef return 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 checking if (xRef.current.$isLoaded) ...

[!NOTE] Introduce ref-based hooks for non-reactive access to CoValues/accounts, add useRef to 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 internal useSubscriptionRef.
    • Expose useRef from createCoValueSubscriptionContext and createAccountSubscriptionContext.
    • Refine selector utilities: useSubscriptionSelector updates; identitySelector helper.
    • Export new types CoValueRef, MaybeLoadedCoValueRef and wire through react and react-native packages.
    • Minor: add CoValueLoadingState namespace types; remove unused context util.
  • Examples (music-player)
    • Replace useCoState usages with useCoStateAndRef and context useAccountRef to 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 shallowEqual helper in lib/utils for selector equality.
    • AccountProvider now exports useAccountRef.
  • Tests
    • Add comprehensive tests for new hooks and contexts (useCoValueRef, useAccountRef, useCoStateAndRef, useAccountAndRef, subscription contexts, selector behavior); add useRenderCount test util.
  • Changeset
    • Patch release for jazz-tools describing new hooks and provider useRef.

Written by Cursor Bugbot for commit ecf74832807b733b99e47025c0ed88d9176899fa. This will update automatically on new commits. Configure here.

Gabrola avatar Nov 08 '25 22:11 Gabrola

@Gabrola is attempting to deploy a commit to the Garden Computing Team on Vercel.

A member of the Team first needs to authorize it.

vercel[bot] avatar Nov 08 '25 22:11 vercel[bot]

@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.

Gabrola avatar Nov 15 '25 20:11 Gabrola