Skip to content

Commit 5caebd1

Browse files
fix(richtext-lexical): prevent TS CodeBlocks from sharing Monaco model (useId) (#14351)
### Changes - Used React’s built-in useId() to give each TypeScript CodeBlock instance a unique Monaco defaultPath. This ensures every TS code block creates its own Monaco model instead of reusing a single shared model. - Exported the Horizontal Rule node from the client exports so it’s available to consumers. ### Why I did it Issue + Video mentioned [here](#13813 (comment)) According to the [@monaco-editor/react documentation](https://www.npmjs.com/package/@monaco-editor/react#multi-model-editor), the path/defaultPath parameter is used as an identifier for the model: > There are three parameters to create a model - value, language and path (monaco.editor.createModel(value, language, monaco.Uri.parse(path))). You can consider last one (path) as an identifier for the model. The Editor component, now, has a path prop. When you specify a path prop, the Editor component checks if it has a model by that path or not. If yes, the existing model will be shown, otherwise, a new one will be created (and stored). When multiple code blocks share the same defaultPath: 'file.tsx', @monaco-editor/react treats them as the same model identifier, causing: - Model reuse across different code block instances - Content synchronization between blocks - Loss of independent state per block
1 parent bc6228c commit 5caebd1

File tree

2 files changed

+15
-4
lines changed
  • packages/richtext-lexical/src

2 files changed

+15
-4
lines changed

packages/richtext-lexical/src/exports/client/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ export {
135135
InlineBlockNode,
136136
} from '../../features/blocks/client/nodes/InlineBlocksNode.js'
137137

138+
export {
139+
$createHorizontalRuleNode,
140+
$isHorizontalRuleNode,
141+
HorizontalRuleNode,
142+
} from '../../features/horizontalRule/client/nodes/HorizontalRuleNode.js'
143+
138144
export { FieldsDrawer } from '../../utilities/fieldsDrawer/Drawer.js'
139145
export { useLexicalDocumentDrawer } from '../../utilities/fieldsDrawer/useLexicalDocumentDrawer.js'
140146
export { useLexicalDrawer } from '../../utilities/fieldsDrawer/useLexicalDrawer.js'

packages/richtext-lexical/src/features/blocks/premade/CodeBlock/Component/Code.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import type { CodeFieldClient, CodeFieldClientProps } from 'payload'
44

55
import { CodeField, useFormFields } from '@payloadcms/ui'
6-
import React, { useMemo } from 'react'
6+
import React, { useId, useMemo } from 'react'
77

88
import { defaultLanguages } from './defaultLanguages.js'
99

@@ -78,6 +78,10 @@ export const CodeComponent: React.FC<AdditionalCodeComponentProps & CodeFieldCli
7878
const language: string =
7979
(languageField?.value as string) || (languageField?.initialValue as string) || 'typescript'
8080

81+
// unique id per component instance to ensure Monaco creates a distinct model
82+
// for each TypeScript code block. Using React's useId is SSR-safe and builtin.
83+
const instanceId = useId()
84+
8185
const label = languages[language]
8286

8387
const props: CodeFieldClient = useMemo<CodeFieldClient>(
@@ -89,13 +93,14 @@ export const CodeComponent: React.FC<AdditionalCodeComponentProps & CodeFieldCli
8993
editorOptions: {},
9094
editorProps: {
9195
// If typescript is set, @monaco-editor/react needs to set the URI to a .ts or .tsx file when it calls createModel().
92-
// This is done through the `defaultPath` prop.
93-
defaultPath: language === 'ts' ? 'file.tsx' : undefined,
96+
// Provide a unique defaultPath per instance so Monaco doesn't reuse the same model
97+
// across multiple code block instances. We use field.name + instanceId for debugability.
98+
defaultPath: language === 'ts' ? `file-${field.name}-${instanceId}.tsx` : undefined,
9499
},
95100
language,
96101
},
97102
}),
98-
[field, language],
103+
[field, language, instanceId],
99104
)
100105

101106
const key = `${field.name}-${language}-${label}`

0 commit comments

Comments
 (0)