Skip to content

fix(web): memoize Zero provider opts to prevent reconnect churn#1230

Merged
MODSetter merged 1 commit intoMODSetter:devfrom
xr843:fix/memoize-zero-provider-opts
Apr 15, 2026
Merged

fix(web): memoize Zero provider opts to prevent reconnect churn#1230
MODSetter merged 1 commit intoMODSetter:devfrom
xr843:fix/memoize-zero-provider-opts

Conversation

@xr843
Copy link
Copy Markdown
Contributor

@xr843 xr843 commented Apr 15, 2026

Summary

Closes #1097.

ZeroProvider rebuilt its opts object and its derived context on
every render, then spread them into <ZeroReactProvider>. If the
underlying Rocicorp Zero provider compares props by reference
(internally or for effect deps), that can trigger extra reconnects or
state churn every time anything above ZeroProvider re-renders.

Change

Wrap opts and context in useMemo:

const context = useMemo(
  () => (hasUser ? { userId: String(userId) } : undefined),
  [hasUser, userId],
);

const opts = useMemo(
  () => ({ userID, schema, queries, context, cacheURL, auth }),
  [userID, context, auth],
);

auth comes from getBearerToken() which returns a string (a
primitive), so reference equality already holds for unchanged tokens —
no useMemo needed. When the token changes, auth becomes a different
string and opts is rebuilt as expected.

schema, queries, and cacheURL are module-level constants so they
are already stable across renders.

Testing

  • Static analysis only — I do not have the Zero sync running locally.
    Please verify that the existing Zero sync / subscription behavior is
    unchanged for both anonymous and authenticated sessions.

Acceptance criteria (from #1097)

  • opts is memoized with useMemo
  • context is a stable reference
  • auth read path unchanged (primitive comparison keeps reference
    stable without an extra useMemo)

High-level PR Summary

This PR fixes a performance issue in the ZeroProvider component where the opts and context objects were being recreated on every render, causing unnecessary reconnections to the Zero provider. The fix wraps both objects in useMemo hooks with appropriate dependencies to ensure stable references across renders, preventing connection churn when parent components re-render.

⏱️ Estimated Review Time: 5-15 minutes

💡 Review Order Suggestion
Order File Path
1 surfsense_web/components/providers/ZeroProvider.tsx

Need help? Join our Discord

Analyze latest changes

Wrap the opts object and derived context in useMemo so ZeroReactProvider
receives stable references across parent re-renders. Before this change
opts was rebuilt on every render of ZeroProvider, which can cause the
Rocicorp Zero client to churn its internal state / reconnect if it
compares props by reference.

Fixes MODSetter#1097.
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 15, 2026

@xr843 is attempting to deploy a commit to the Rohan Verma's projects Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Copy Markdown

@recurseml recurseml bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review by RecurseML

🔍 Review performed on 656e061..68e6acd

✨ No bugs found, your code is sparkling clean

✅ Files analyzed, no issues (1)

surfsense_web/components/providers/ZeroProvider.tsx

@MODSetter MODSetter merged commit 5bbb7b9 into MODSetter:dev Apr 15, 2026
7 of 10 checks passed
@xr843 xr843 deleted the fix/memoize-zero-provider-opts branch April 16, 2026 00:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants