fix: use useId for auto-generated editor id to fix SSR hydration - #495
Open
jawadakram20 wants to merge 1 commit into
Open
fix: use useId for auto-generated editor id to fix SSR hydration#495jawadakram20 wants to merge 1 commit into
jawadakram20 wants to merge 1 commit into
Conversation
The auto-generated editor id came from a module-level counter
(`editor-${++win.__unlayer_lastEditorId}`). On a warm SSR server the
counter keeps climbing across requests while every fresh client starts at
0, so the server and client render different ids. React does not patch the
attribute mismatch on hydration, and the mount effect then calls
`unlayer.createEditor` against the stale server id — a div that no longer
matches — leaving a silently blank editor under SSR (e.g. the Next.js App
Router, which this package already targets via the `'use client'` banner).
Use React 18+'s `useId`, whose value is identical on the server render and
during client hydration. React 16.8/17 (still in the peer range) have no
useId and keep the legacy counter fallback. Passing an explicit `editorId`
is unaffected.
Adds test/ssr.test.tsx, which reproduces the hydration mismatch (fails on
the old counter, passes with useId).
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.
Fix SSR hydration mismatch for the auto-generated editor id
The problem
When
<EmailEditor />is rendered without an expliciteditorId, the id is generated from a module-level counter:Under SSR this diverges between server and client:
editor-2,editor-3, ….0, so the client computeseditor-1.React does not patch attribute mismatches during hydration, so the DOM keeps the server's id while the mount effect calls
unlayer.createEditor({ id: 'editor-1' })against an element that no longer matches — the editor mounts on a missing container and renders blank, with no error.This bites the Next.js App Router specifically, which this package already targets via the
'use client'banner added in 2.0.0.The fix
Use React 18+'s
useId, whose value is identical on the server render and during client hydration. React 16.8/17 (still in the peer range) have nouseId, so they keep the legacy counter fallback. The implementation is selected once at module load, so the same hook runs on every render (rules of hooks are respected). Passing an expliciteditorIdis unaffected.Testing
Adds
test/ssr.test.tsx, which renders on the server withrenderToString, then hydrates withhydrateRoot, and asserts the id is stable with no hydration-mismatch warning. The test fails on the old counter (server/client id divergence + React hydration warning) and passes withuseId.npm test— 10 passing (8 existing + 2 new)npm run typecheck,npm run build,npm run lint— cleannpm run test:coverage— above thresholds (the legacy fallback isv8 ignore-d since it is exercised only by the React 16/17test:legacysmoke suite, which runs without coverage)Notes
editorIdstill wins when provided.useIdoutput is stripped of:so the generated id stays a valid CSS selector forunlayer.createEditor.