Skip to content

Commit e0df0d6

Browse files
authored
fix(richtext-lexical): properly handle error if blocks or inline blocks are not found (#10497)
Fixes #10445 Previously, if a block or inline block was part of the editor data that did not exist in the config, an error would be thrown that rendered the entire editor unusable. Now, the error is handled gracefully and displayed within the block, allowing users to remove the block in order to fix the issue. ![CleanShot 2025-01-10 at 10 17 14@2x](https://github.com/user-attachments/assets/7d1c97bc-8c7b-451d-a6dc-e46d39dfb0f5)
1 parent 8d5d2d1 commit e0df0d6

File tree

4 files changed

+70
-23
lines changed

4 files changed

+70
-23
lines changed

packages/richtext-lexical/src/features/blocks/client/component/index.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
z-index: 1;
77
}
88

9+
.lexical-block-not-found {
10+
color: var(--theme-error-500);
11+
font-size: 1.1rem;
12+
}
13+
914
.lexical-block {
1015
display: flex;
1116
flex-direction: column;

packages/richtext-lexical/src/features/blocks/client/component/index.tsx

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,11 @@ export const BlockComponent: React.FC<Props> = (props) => {
174174

175175
const clientSchemaMap = featureClientSchemaMap['blocks']
176176

177-
const blocksField: BlocksFieldClient = clientSchemaMap[
177+
const blocksField: BlocksFieldClient | undefined = clientSchemaMap[
178178
componentMapRenderedBlockPath
179-
][0] as BlocksFieldClient
179+
]?.[0] as BlocksFieldClient
180180

181-
const clientBlock = blocksField.blocks[0]
181+
const clientBlock = blocksField?.blocks?.[0]
182182

183183
const { i18n, t } = useTranslation<object, string>()
184184

@@ -352,13 +352,15 @@ export const BlockComponent: React.FC<Props> = (props) => {
352352
() =>
353353
({
354354
children,
355+
disableBlockName,
355356
editButton,
356357
errorCount,
357358
fieldHasErrors,
358359
Label,
359360
removeButton,
360361
}: {
361362
children?: React.ReactNode
363+
disableBlockName?: boolean
362364
editButton?: boolean
363365
errorCount?: number
364366
fieldHasErrors?: boolean
@@ -385,12 +387,15 @@ export const BlockComponent: React.FC<Props> = (props) => {
385387
className={`${baseClass}__block-pill ${baseClass}__block-pill-${formData?.blockType}`}
386388
pillStyle="white"
387389
>
388-
{blockDisplayName}
390+
{blockDisplayName ?? formData?.blockType}
389391
</Pill>
390-
<SectionTitle
391-
path="blockName"
392-
readOnly={parentLexicalRichTextField?.admin?.readOnly || false}
393-
/>
392+
{!disableBlockName && (
393+
<SectionTitle
394+
path="blockName"
395+
readOnly={parentLexicalRichTextField?.admin?.readOnly || false}
396+
/>
397+
)}
398+
394399
{fieldHasErrors && (
395400
<ErrorPill count={errorCount ?? 0} i18n={i18n} withMessage />
396401
)}
@@ -444,7 +449,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
444449
{initialState ? (
445450
<>
446451
<RenderFields
447-
fields={clientBlock.fields}
452+
fields={clientBlock?.fields}
448453
forceRender
449454
parentIndexPath=""
450455
parentPath="" // See Blocks feature path for details as for why this is empty
@@ -463,7 +468,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
463468
drawerSlug,
464469
blockDisplayName,
465470
t,
466-
clientBlock.fields,
471+
clientBlock?.fields,
467472
schemaFieldsPath,
468473
permissions,
469474
// DO NOT ADD FORMDATA HERE! Adding formData will kick you out of sub block editors while writing.
@@ -483,7 +488,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
483488
return await onChange({ formState, submit: true })
484489
},
485490
]}
486-
fields={clientBlock.fields}
491+
fields={clientBlock?.fields}
487492
initialState={initialState}
488493
onChange={[onChange]}
489494
onSubmit={(formState) => {
@@ -508,7 +513,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
508513
CustomBlock={CustomBlock}
509514
EditButton={EditButton}
510515
errorCount={errorCount}
511-
formSchema={clientBlock.fields}
516+
formSchema={clientBlock?.fields}
512517
initialState={initialState}
513518
nodeKey={nodeKey}
514519
RemoveButton={RemoveButton}
@@ -524,13 +529,23 @@ export const BlockComponent: React.FC<Props> = (props) => {
524529
editor,
525530
errorCount,
526531
toggleDrawer,
527-
clientBlock.fields,
532+
clientBlock?.fields,
528533
// DO NOT ADD FORMDATA HERE! Adding formData will kick you out of sub block editors while writing.
529534
initialState,
530535
nodeKey,
531536
onChange,
532537
submitted,
533538
])
534539

540+
if (!clientBlock) {
541+
return (
542+
<BlockCollapsible disableBlockName={true} fieldHasErrors={true}>
543+
<div className="lexical-block-not-found">
544+
Error: Block '{formData.blockType}' not found in the config but exists in the lexical data
545+
</div>
546+
</BlockCollapsible>
547+
)
548+
}
549+
535550
return Block
536551
}

packages/richtext-lexical/src/features/blocks/client/componentInline/index.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
display: inline-block;
66
}
77

8+
.inline-block.inline-block-not-found {
9+
max-width: calc(var(--base) * 20);
10+
color: var(--theme-error-500);
11+
}
12+
813
.inline-block {
914
@extend %body;
1015
@include shadow-sm;

packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,21 +126,27 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
126126

127127
const blocksField: BlocksFieldClient = clientSchemaMap[
128128
componentMapRenderedBlockPath
129-
][0] as BlocksFieldClient
129+
]?.[0] as BlocksFieldClient
130130

131-
const clientBlock = blocksField.blocks[0]
131+
const clientBlock = blocksField?.blocks?.[0]
132132

133133
// Open drawer on "mount"
134134
useEffect(() => {
135135
if (!firstTimeDrawer.current && createdInlineBlock?.getKey() === nodeKey) {
136136
// > 2 because they always have "id" and "blockName" fields
137-
if (clientBlock.fields.length > 2) {
137+
if (clientBlock?.fields?.length > 2) {
138138
toggleDrawer()
139139
}
140140
setCreatedInlineBlock?.(undefined)
141141
firstTimeDrawer.current = true
142142
}
143-
}, [clientBlock.fields.length, createdInlineBlock, nodeKey, setCreatedInlineBlock, toggleDrawer])
143+
}, [
144+
clientBlock?.fields?.length,
145+
createdInlineBlock,
146+
nodeKey,
147+
setCreatedInlineBlock,
148+
toggleDrawer,
149+
])
144150

145151
const removeInlineBlock = useCallback(() => {
146152
editor.update(() => {
@@ -199,7 +205,7 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
199205
}, [clearSelection, editor, isSelected, nodeKey, $onDelete, setSelected, onClick])
200206

201207
const blockDisplayName = clientBlock?.labels?.singular
202-
? getTranslation(clientBlock.labels.singular, i18n)
208+
? getTranslation(clientBlock?.labels.singular, i18n)
203209
: clientBlock?.slug
204210

205211
const onChangeAbortControllerRef = useRef(new AbortController())
@@ -355,12 +361,13 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
355361

356362
const InlineBlockContainer = useMemo(
357363
() =>
358-
({ children }: { children: React.ReactNode }) => (
364+
({ children, className }: { children: React.ReactNode; className?: string }) => (
359365
<div
360366
className={[
361367
baseClass,
362368
baseClass + '-' + formData.blockType,
363369
isSelected && `${baseClass}--selected`,
370+
className,
364371
]
365372
.filter(Boolean)
366373
.join(' ')}
@@ -376,9 +383,24 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
376383
if (CustomLabel) {
377384
return () => CustomLabel
378385
} else {
379-
return () => <div>{getTranslation(clientBlock.labels!.singular, i18n)}</div>
386+
return () => (
387+
<div>{clientBlock?.labels ? getTranslation(clientBlock?.labels.singular, i18n) : ''}</div>
388+
)
380389
}
381-
}, [CustomLabel, clientBlock.labels, i18n])
390+
}, [CustomLabel, clientBlock?.labels, i18n])
391+
392+
if (!clientBlock) {
393+
return (
394+
<InlineBlockContainer className={`${baseClass}-not-found`}>
395+
<span>Error: Block '{formData.blockType}' not found</span>
396+
{editor.isEditable() ? (
397+
<div className={`${baseClass}__actions`}>
398+
<RemoveButton />
399+
</div>
400+
) : null}
401+
</InlineBlockContainer>
402+
)
403+
}
382404

383405
return (
384406
<Form
@@ -389,7 +411,7 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
389411
},
390412
]}
391413
disableValidationOnSubmit
392-
fields={clientBlock.fields}
414+
fields={clientBlock?.fields}
393415
initialState={initialState || {}}
394416
onChange={[onChange]}
395417
onSubmit={(formState) => {
@@ -409,7 +431,7 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
409431
{initialState ? (
410432
<>
411433
<RenderFields
412-
fields={clientBlock.fields}
434+
fields={clientBlock?.fields}
413435
forceRender
414436
parentIndexPath=""
415437
parentPath="" // See Blocks feature path for details as for why this is empty

0 commit comments

Comments
 (0)