Skip to content

Commit 1b17df9

Browse files
fix(richtext-lexical): ensure state is up-to-date on inline-block restore (#12128)
### What? Ensures that the initial state on inline blocks gets updated when an inline block gets restored from lexical history. ### Why? If an inline block got edited, removed, and restored (via lexical undo), the state of the inline block was taken from an outdated initial state and did not reflect the current form state, see screencast https://github.com/user-attachments/assets/6f55ded3-57bc-4de0-8ac1-e49331674d5f ### How? We now ensure that the initial state gets re-initialized after the component got unmounted, resulting in the expected behavior: https://github.com/user-attachments/assets/4e97eeb2-6dc4-49b1-91ca-35b59a93a348 --------- Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
1 parent 3df1329 commit 1b17df9

File tree

2 files changed

+63
-1
lines changed
  • packages/richtext-lexical/src/features/blocks/client/componentInline
  • test/lexical/collections/Lexical/e2e/blocks

2 files changed

+63
-1
lines changed

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,10 +284,22 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
284284
)
285285
// cleanup effect
286286
useEffect(() => {
287+
const isStateOutOfSync = (formData: InlineBlockFields, initialState: FormState) => {
288+
return Object.keys(initialState).some(
289+
(key) => initialState[key] && formData[key] !== initialState[key].value,
290+
)
291+
}
292+
287293
return () => {
294+
// If the component is unmounted (either via removeInlineBlock or via lexical itself) and the form state got changed before,
295+
// we need to reset the initial state to force a re-fetch of the initial state when it gets mounted again (e.g. via lexical history undo).
296+
// Otherwise it would use an outdated initial state.
297+
if (initialState && isStateOutOfSync(formData, initialState)) {
298+
setInitialState(false)
299+
}
288300
abortAndIgnore(onChangeAbortControllerRef.current)
289301
}
290-
}, [])
302+
}, [formData, initialState])
291303

292304
/**
293305
* HANDLE FORM SUBMIT

test/lexical/collections/Lexical/e2e/blocks/e2e.spec.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,5 +1666,55 @@ describe('lexicalBlocks', () => {
16661666
},
16671667
})
16681668
})
1669+
1670+
test('ensure inline blocks restore their state after undoing a removal', async () => {
1671+
await page.goto('http://localhost:3000/admin/collections/LexicalInBlock?limit=10')
1672+
1673+
await page.locator('.cell-id a').first().click()
1674+
await page.waitForURL(`**/collections/LexicalInBlock/**`)
1675+
1676+
// Wait for the page to be fully loaded and elements to be stable
1677+
await page.waitForLoadState('domcontentloaded')
1678+
1679+
// Wait for the specific row to be visible and have its content loaded
1680+
const row2 = page.locator('#blocks-row-2')
1681+
await expect(row2).toBeVisible()
1682+
1683+
// Get initial count and ensure it's stable
1684+
const inlineBlocks = page.locator('#blocks-row-2 .inline-block-container')
1685+
const inlineBlockCount = await inlineBlocks.count()
1686+
await expect(() => {
1687+
expect(inlineBlockCount).toBeGreaterThan(0)
1688+
}).toPass()
1689+
1690+
const inlineBlockElement = inlineBlocks.first()
1691+
await inlineBlockElement.locator('.inline-block__editButton').first().click()
1692+
1693+
await page.locator('.drawer--is-open #field-text').fill('value1')
1694+
await page.locator('.drawer--is-open button[type="submit"]').first().click()
1695+
1696+
// remove inline block
1697+
await inlineBlockElement.click()
1698+
await page.keyboard.press('Backspace')
1699+
1700+
// Check both that this specific element is removed and the total count decreased
1701+
await expect(inlineBlocks).toHaveCount(inlineBlockCount - 1)
1702+
1703+
await page.keyboard.press('Escape')
1704+
1705+
await inlineBlockElement.click()
1706+
1707+
// Undo the removal using keyboard shortcut
1708+
await page.keyboard.press('ControlOrMeta+Z')
1709+
1710+
// Wait for the block to be restored
1711+
await expect(inlineBlocks).toHaveCount(inlineBlockCount)
1712+
1713+
// Open the drawer again
1714+
await inlineBlockElement.locator('.inline-block__editButton').first().click()
1715+
1716+
// Check if the text field still contains 'value1'
1717+
await expect(page.locator('.drawer--is-open #field-text')).toHaveValue('value1')
1718+
})
16691719
})
16701720
})

0 commit comments

Comments
 (0)