Skip to content

Commit bfa5055

Browse files
authored
Fix rendering issues when both chunking and React's strict mode are enabled (ianstormtaylor#5988)
1 parent eae2474 commit bfa5055

File tree

6 files changed

+99
-39
lines changed

6 files changed

+99
-39
lines changed

.changeset/warm-months-hammer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'slate-react': patch
3+
---
4+
5+
Fix rendering issues when both chunking and React's strict mode are enabled

packages/slate-react/src/chunking/reconcile-children.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ export const reconcileChildren = (
3131
debug,
3232
}: ReconcileOptions
3333
) => {
34-
chunkTree.modifiedChunks.clear()
35-
3634
const chunkTreeHelper = new ChunkTreeHelper(chunkTree, { chunkSize, debug })
3735
const childrenHelper = new ChildrenHelper(editor, children)
3836

packages/slate-react/src/components/chunk-tree.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { Fragment } from 'react'
1+
import React, { ComponentProps, Fragment, useEffect } from 'react'
22
import { Element } from 'slate'
33
import { Key } from 'slate-dom'
44
import { RenderChunkProps } from './editable'
@@ -51,7 +51,17 @@ const ChunkAncestor = <C extends TChunkAncestor>(props: {
5151
})
5252
}
5353

54-
const ChunkTree = ChunkAncestor<TChunkTree>
54+
const ChunkTree = (props: ComponentProps<typeof ChunkAncestor<TChunkTree>>) => {
55+
// Clear the set of modified chunks only when React finishes rendering. The
56+
// timing of this is important in strict mode because if the chunks are
57+
// cleared during rendering (such as in reconcileChildren), strict mode's
58+
// second render won't include them.
59+
useEffect(() => {
60+
props.root.modifiedChunks.clear()
61+
})
62+
63+
return <ChunkAncestor {...props} />
64+
}
5565

5666
const MemoizedChunk = React.memo(
5767
ChunkAncestor<TChunk>,

site/examples/js/huge-document.jsx

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { faker } from '@faker-js/faker'
2-
import React, { useCallback, useEffect, useState } from 'react'
2+
import React, { StrictMode, useCallback, useEffect, useState } from 'react'
33
import { createEditor as slateCreateEditor, Editor } from 'slate'
44
import { Editable, Slate, withReact, useSelected } from 'slate-react'
55

@@ -41,6 +41,7 @@ const initialConfig = {
4141
'chunk'
4242
),
4343
showSelectedHeadings: parseBoolean('selected_headings', false),
44+
strictMode: parseBoolean('strict', false),
4445
}
4546
const setSearchParams = config => {
4647
if (searchParams) {
@@ -54,6 +55,7 @@ const setSearchParams = config => {
5455
'selected_headings',
5556
config.showSelectedHeadings ? 'true' : 'false'
5657
)
58+
searchParams.set('strict', config.strictMode ? 'true' : 'false')
5759
history.replaceState({}, '', `?${searchParams.toString()}`)
5860
}
5961
}
@@ -129,6 +131,24 @@ const HugeDocumentExample = () => {
129131
),
130132
[config.contentVisibilityMode, config.chunkOutlines]
131133
)
134+
const editable = rendering ? (
135+
<div>Rendering&hellip;</div>
136+
) : (
137+
<Slate key={editorVersion} editor={editor} initialValue={initialValue}>
138+
<Editable
139+
placeholder="Enter some text…"
140+
renderElement={renderElement}
141+
renderChunk={config.chunkDivs ? renderChunk : undefined}
142+
spellCheck
143+
autoFocus
144+
/>
145+
</Slate>
146+
)
147+
const editableWithStrictMode = config.strictMode ? (
148+
<StrictMode>{editable}</StrictMode>
149+
) : (
150+
editable
151+
)
132152
return (
133153
<>
134154
<PerformanceControls
@@ -137,19 +157,7 @@ const HugeDocumentExample = () => {
137157
setConfig={setConfig}
138158
/>
139159

140-
{rendering ? (
141-
<div>Rendering&hellip;</div>
142-
) : (
143-
<Slate key={editorVersion} editor={editor} initialValue={initialValue}>
144-
<Editable
145-
placeholder="Enter some text…"
146-
renderElement={renderElement}
147-
renderChunk={config.chunkDivs ? renderChunk : undefined}
148-
spellCheck
149-
autoFocus
150-
/>
151-
</Slate>
152-
)}
160+
{editableWithStrictMode}
153161
</>
154162
)
155163
}
@@ -394,6 +402,21 @@ const PerformanceControls = ({ editor, config, setConfig }) => {
394402
Call <code>useSelected</code> in each heading
395403
</label>
396404
</p>
405+
406+
<p>
407+
<label>
408+
<input
409+
type="checkbox"
410+
checked={config.strictMode}
411+
onChange={event =>
412+
setConfig({
413+
strictMode: event.target.checked,
414+
})
415+
}
416+
/>{' '}
417+
React strict mode (only works in localhost)
418+
</label>
419+
</p>
397420
</details>
398421

399422
<details>

site/examples/js/richtext.jsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,9 @@ const BlockButton = ({ format, icon }) => {
194194
format,
195195
isAlignType(format) ? 'align' : 'type'
196196
)}
197-
onMouseDown={event => {
198-
event.preventDefault()
199-
toggleBlock(editor, format)
200-
}}
197+
onPointerDown={event => event.preventDefault()}
198+
onClick={() => toggleBlock(editor, format)}
199+
data-test-id={`block-button-${format}`}
201200
>
202201
<Icon>{icon}</Icon>
203202
</Button>
@@ -208,10 +207,8 @@ const MarkButton = ({ format, icon }) => {
208207
return (
209208
<Button
210209
active={isMarkActive(editor, format)}
211-
onMouseDown={event => {
212-
event.preventDefault()
213-
toggleMark(editor, format)
214-
}}
210+
onPointerDown={event => event.preventDefault()}
211+
onClick={() => toggleMark(editor, format)}
215212
>
216213
<Icon>{icon}</Icon>
217214
</Button>

site/examples/ts/huge-document.tsx

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { faker } from '@faker-js/faker'
22
import React, {
33
CSSProperties,
44
Dispatch,
5+
StrictMode,
56
useCallback,
67
useEffect,
78
useState,
@@ -33,6 +34,7 @@ interface Config {
3334
chunkOutlines: boolean
3435
contentVisibilityMode: 'none' | 'element' | 'chunk'
3536
showSelectedHeadings: boolean
37+
strictMode: boolean
3638
}
3739

3840
const blocksOptions = [
@@ -78,6 +80,7 @@ const initialConfig: Config = {
7880
'chunk'
7981
),
8082
showSelectedHeadings: parseBoolean('selected_headings', false),
83+
strictMode: parseBoolean('strict', false),
8184
}
8285

8386
const setSearchParams = (config: Config) => {
@@ -92,6 +95,7 @@ const setSearchParams = (config: Config) => {
9295
'selected_headings',
9396
config.showSelectedHeadings ? 'true' : 'false'
9497
)
98+
searchParams.set('strict', config.strictMode ? 'true' : 'false')
9599
history.replaceState({}, '', `?${searchParams.toString()}`)
96100
}
97101
}
@@ -183,6 +187,26 @@ const HugeDocumentExample = () => {
183187
[config.contentVisibilityMode, config.chunkOutlines]
184188
)
185189

190+
const editable = rendering ? (
191+
<div>Rendering&hellip;</div>
192+
) : (
193+
<Slate key={editorVersion} editor={editor} initialValue={initialValue}>
194+
<Editable
195+
placeholder="Enter some text…"
196+
renderElement={renderElement}
197+
renderChunk={config.chunkDivs ? renderChunk : undefined}
198+
spellCheck
199+
autoFocus
200+
/>
201+
</Slate>
202+
)
203+
204+
const editableWithStrictMode = config.strictMode ? (
205+
<StrictMode>{editable}</StrictMode>
206+
) : (
207+
editable
208+
)
209+
186210
return (
187211
<>
188212
<PerformanceControls
@@ -191,19 +215,7 @@ const HugeDocumentExample = () => {
191215
setConfig={setConfig}
192216
/>
193217

194-
{rendering ? (
195-
<div>Rendering&hellip;</div>
196-
) : (
197-
<Slate key={editorVersion} editor={editor} initialValue={initialValue}>
198-
<Editable
199-
placeholder="Enter some text…"
200-
renderElement={renderElement}
201-
renderChunk={config.chunkDivs ? renderChunk : undefined}
202-
spellCheck
203-
autoFocus
204-
/>
205-
</Slate>
206-
)}
218+
{editableWithStrictMode}
207219
</>
208220
)
209221
}
@@ -483,6 +495,21 @@ const PerformanceControls = ({
483495
Call <code>useSelected</code> in each heading
484496
</label>
485497
</p>
498+
499+
<p>
500+
<label>
501+
<input
502+
type="checkbox"
503+
checked={config.strictMode}
504+
onChange={event =>
505+
setConfig({
506+
strictMode: event.target.checked,
507+
})
508+
}
509+
/>{' '}
510+
React strict mode (only works in localhost)
511+
</label>
512+
</p>
486513
</details>
487514

488515
<details>

0 commit comments

Comments
 (0)