Skip to content

Optimize client settings selector rerenders#3745

Draft
cursor[bot] wants to merge 3 commits into
mainfrom
cursor/component-performance-optimization-298e
Draft

Optimize client settings selector rerenders#3745
cursor[bot] wants to merge 3 commits into
mainfrom
cursor/component-performance-optimization-298e

Conversation

@cursor

@cursor cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What Changed

  • Added selected-snapshot caching for useClientSettings / merged settings hooks so consumers keep the previous selected reference when their selected value is shallow-equal.
  • Added unit coverage for unrelated client settings updates, including the project-grouping selector that returns a derived object.

Why

A manual React performance pass found that settings selector consumers subscribed to the entire client settings object. Updating an unrelated client setting, such as favorites, caused selected consumers like ProjectGroupingConsumer and ThreadSortConsumer to rerender even though their selected values were unchanged.

react-doctor was run with corepack pnpm dlx react-doctor@latest . --verbose --category performance --no-telemetry; it reported broader performance findings, while this PR keeps one focused fix for the settings selector fanout found during the manual pass.

UI Changes

Before/after React Scan recordings were captured with a temporary Vite fixture using the real settings hooks, then the fixture was removed before commit:

  • Before recording: before_settings_selector_rerenders.mp4 — five unrelated favorites updates increased both selected consumer counters from 2 to 12, with React Scan purple render highlights on the consumer panels.
  • After recording: after_settings_selector_rerenders.mp4 — the same five updates left both counters stable at 2, with no React Scan highlights on the consumer panels.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Validation:

  • corepack pnpm --filter @t3tools/web test -- src/hooks/useSettings.test.ts passed (web unit project: 148 files / 1280 tests)
  • NODE_OPTIONS="--experimental-strip-types" corepack pnpm exec vp check passed with existing warnings
  • corepack pnpm exec vp run typecheck passed
Open in Web View Automation 

Note

Reduce rerenders in client settings hooks by stabilizing selector references

  • Adds areSettingsSelectionsEqual, a shallow equality comparator, and createSettingsSelectorSnapshotReader, a factory that returns the previous selection reference when the selected slice is shallowly equal across updates.
  • Introduces useSelectedClientSettings, a new hook that composes useSyncExternalStore with the snapshot reader to avoid returning new references when unrelated settings change.
  • Reworks useClientSettings and useMergedSettings to delegate to useSelectedClientSettings, so components only re-render when their selected slice actually changes.
  • Behavioral Change: useClientSettings and useMergedSettings now use shallow equality instead of referential equality to determine whether to propagate a new value, which may suppress re-renders that previously occurred.
📊 Macroscope summarized c19a486. 1 file reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

cursoragent and others added 3 commits July 6, 2026 16:19
Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>
Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>
Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Jul 6, 2026
return typeof value === "object" && value !== null;
}

export function areSettingsSelectionsEqual<T>(left: T, right: T): boolean {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium hooks/useSettings.ts:51

areSettingsSelectionsEqual uses Object.keys to compare non-primitive selections, so Set, Map, Date, and class instances with no enumerable own keys all compare equal to each other. When a selector returns one of these (e.g. useClientSettings((s) => new Set(s.favorites.map(...)))), createSettingsSelectorSnapshotReader treats every new selection as equal to the stale cached one and keeps returning the previous value, so the component stops updating after the first render. Consider falling back to Object.is for non-plain objects so distinct object identities still produce updates.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/hooks/useSettings.ts around line 51:

`areSettingsSelectionsEqual` uses `Object.keys` to compare non-primitive selections, so `Set`, `Map`, `Date`, and class instances with no enumerable own keys all compare equal to each other. When a selector returns one of these (e.g. `useClientSettings((s) => new Set(s.favorites.map(...)))`), `createSettingsSelectorSnapshotReader` treats every new selection as equal to the stale cached one and keeps returning the previous value, so the component stops updating after the first render. Consider falling back to `Object.is` for non-plain objects so distinct object identities still produce updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant