[sitecore-jss-react] RichText: stabilize dangerouslySetInnerHTML reference across re-renders#2204
Open
pzi wants to merge 1 commit into
Open
[sitecore-jss-react] RichText: stabilize dangerouslySetInnerHTML reference across re-renders#2204pzi wants to merge 1 commit into
pzi wants to merge 1 commit into
Conversation
…ence across re-renders RichText built a new dangerouslySetInnerHTML object on every render. React DOM compares this prop by reference, so every parent re-render re-set innerHTML, recreating all child DOM nodes and dropping the internal-link click/prefetch listeners attached by NextRichText. Memoize the object keyed on the resolved html string so the reference stays stable when content is unchanged. Also dedupe react/react-dom in sitecore-jss-nextjs test setup: with yarn nmHoistingLimits: workspaces, the workspace-linked react package resolves its own React copy, breaking hooks in its components under this package's react-dom. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pzi
force-pushed
the
fix/react-richtext-stable-innerhtml
branch
from
July 4, 2026 18:31
b1b25ce to
8d5080b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description / Motivation
Fixes #2203.
RichTextinsitecore-jss-reactbuilt itsdangerouslySetInnerHTMLprop inline on every render, creating a new object reference each time the component function ran — even when the rendered html was unchanged.React DOM compares the
dangerouslySetInnerHTMLprop by reference (not by the nested__htmlstring) for plain host elements, and unconditionally re-setsdomElement.innerHTMLwhen the reference changes. As a result, every parent re-render forced a fullinnerHTMLreplacement, destroying and recreating every child DOM node inside the rich text.This is especially problematic in combination with
sitecore-jss-nextjs'sNextRichText, which attaches internal-linkclick/prefetch listeners imperatively via auseEffectkeyed only onhasText. SincehasTextdoesn't change across re-renders, the effect never reruns — so after any unrelated ancestor re-render, the freshly recreated<a>nodes silently lose their SPA routing/prefetch behavior.Symptoms:
mousedownandclick) break clicks.innerHTMLrewrite on every incidental re-render.Fix
Memoize the
dangerouslySetInnerHTMLobject inRichTextwithuseMemo, keyed on the resolved html string (field.editable/field.valuedepending oneditable). The object reference now stays stable across re-renders when content is unchanged — including when thefieldobject itself is a new instance with the same value — so React DOM skips theinnerHTMLre-set and existing DOM nodes (and their listeners) are preserved. When the html actually changes, behavior is unchanged: content is re-rendered as before.The memoization is placed before the early
nullreturn to comply with the Rules of Hooks.Test-infrastructure change (
sitecore-jss-nextjs/test/setup.js)This is the first hook used by a
sitecore-jss-reactcomponent thatsitecore-jss-nextjsunit tests render. Because the monorepo usesnmHoistingLimits: workspaces, each workspace resolves its own physical copy ofreact— so the workspace-linkedsitecore-jss-reactdist ran its hooks against a different React instance than thereact-domrendering the tests, throwingTypeError: Cannot read properties of null (reading 'useMemo').The mocha setup now redirects
react/react-domresolution to a single copy for the test run. This affects unit tests only; consuming apps have a single React via peer dependencies and are unaffected.Changes
packages/sitecore-jss-react/src/components/RichText.tsx— memoizedangerouslySetInnerHTMLkeyed on the resolved html string.packages/sitecore-jss-react/src/components/RichText.test.tsx— new tests: DOM content is not replaced on re-render with an unchanged value (same or newfieldobject), and is replaced when the value changes.packages/sitecore-jss-nextjs/test/setup.js— dedupereact/react-dommodule resolution in the mocha test environment (see above).Testing Details
New test reproducing the bug fails without the fix (
AssertionError: expected <a href="/foo"></a> to equal <a href="/foo"></a>— anchor node replaced on re-render) and passes with it. A second test verifies the DOM is replaced when the field value actually changes.packages/sitecore-jss-react:yarn test— 288 passing,yarn lintclean,yarn buildclean.packages/sitecore-jss-nextjs:yarn test— 324 passing,yarn lintclean.Unit Test Added
Manual Test/Other (Please elaborate)
Types of changes
🤖 Generated with Claude Code