Files
copilotkit__copilotkit/.changeset/fix-messageview-labels-props-freeze.md
Martha Schumann 1a61be49dc refactor(FOR-75): replace useJsonStable with useShallowStableRef; add unit tests and changeset
- Kill useJsonStable in CopilotChatConfigurationProvider — useShallowStableRef
  is now the single stabilization primitive for both messageView props and labels
- Harden useShallowStableRef with isPlainObject guard so arrays, Dates, and
  class instances are never shallow-compared (reference-only for non-plain objects)
- Remove redundant stableParentLabels — parentConfig?.labels is already stabilized
  by the parent provider's own useShallowStableRef
- Uninstall ts-deepmerge from react-core dependencies (no longer imported anywhere)
- Add unit tests for useShallowStableRef in slots.test.ts (5 tests)
- Add changeset (patch) describing the FOR-75 performance fix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 11:00:19 -07:00

1.2 KiB

@copilotkit/react-core
@copilotkit/react-core
patch

fix: stabilize messageView and labels props to prevent message list re-renders on every keystroke

Passing messageView or labels as inline object props to <CopilotChat /> previously caused all completed assistant messages to re-render on every keystroke due to reference instability. This was especially severe with large message histories (DocuSign: 100+ messages reported 2s→16s send time degradation).

Root causes fixed:

  • ts-deepmerge.merge() deep-cloned plain objects even from a single source, creating a new reference every render that defeated MemoizedSlotWrapper's shallow equality check. Replaced with shallow spread + useShallowStableRef.
  • Inline labels objects created a new mergedLabels context value every render, causing all useCopilotChatConfiguration() consumers across every message to re-render. Fixed by stabilizing with useShallowStableRef in CopilotChatConfigurationProvider.

The useShallowStableRef hook (added to slots.tsx) is now the single stabilization primitive: it returns the same reference as long as the value is shallowly equal, with an isPlainObject guard to avoid incorrect equality for arrays, Dates, and class instances.