Skip to content

Commit 83ae5f2

Browse files
authored
Merge pull request #4107 from udecode/fix/plate-controller-fallback-hook-order
Fix `PlateController` hook order error and behaviour when editors have no `id`
2 parents 6b351ee + dc3d72e commit 83ae5f2

File tree

16 files changed

+126
-81
lines changed

16 files changed

+126
-81
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@udecode/slate': patch
3+
---
4+
5+
Change type of `editor.id` from `any` to `string`. The previous value of `any` was causing `NodeIn<Value>['id']` to have type `any`.

.changeset/shaggy-eggs-occur.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@udecode/plate-selection': patch
3+
'@udecode/plate-toggle': patch
4+
---
5+
6+
Expect `NodeIn<Value>['id']` to have type `unknown` since the Node ID plugin may or may not be enabled.

.changeset/thick-socks-do.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@udecode/plate-core': patch
3+
---
4+
5+
- `editor.id` now defaults to `nanoid()` if no `id` was specified when creating the editor.
6+
- Fix: Using Plate hooks such as `useEditorRef` inside PlateController causes React to throw an error about hook order.

packages/core/src/lib/editor/withSlate.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,4 +458,32 @@ describe('withPlate', () => {
458458
]);
459459
});
460460
});
461+
462+
describe('when the previous editor has an id', () => {
463+
it('should use that id', () => {
464+
const oldEditor = withSlate(createEditor());
465+
oldEditor.id = 'old';
466+
const editor = withSlate(oldEditor);
467+
expect(editor.id).toBe('old');
468+
});
469+
});
470+
471+
describe('when the id option is provided', () => {
472+
it('should use that id', () => {
473+
const oldEditor = withSlate(createEditor());
474+
oldEditor.id = 'old';
475+
const editor = withSlate(oldEditor, { id: 'new' });
476+
expect(editor.id).toBe('new');
477+
});
478+
});
479+
480+
describe('when no id is provided', () => {
481+
it('should use a unique id for each editor', () => {
482+
const id1 = withSlate(createEditor()).id;
483+
const id2 = withSlate(createEditor()).id;
484+
expect(id1).toBeTruthy();
485+
expect(id2).toBeTruthy();
486+
expect(id1).not.toEqual(id2);
487+
});
488+
});
461489
});

packages/core/src/lib/editor/withSlate.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { getPluginType, getSlatePlugin } from '../plugin/getSlatePlugin';
1717
import { type CorePlugin, getCorePlugins } from '../plugins/getCorePlugins';
1818

1919
export type BaseWithSlateOptions<P extends AnyPluginConfig = CorePlugin> = {
20-
id?: any;
20+
id?: string;
2121
/**
2222
* Select the editor after initialization.
2323
*
@@ -92,8 +92,7 @@ export const withSlate = <
9292
): TSlateEditor<V, InferPlugins<P[]>> => {
9393
const editor = e as SlateEditor;
9494

95-
// Override incremental id generated by slate
96-
editor.id = id ?? editor.id;
95+
editor.id = id ?? editor.id ?? nanoid();
9796
editor.key = editor.key ?? nanoid();
9897
editor.isFallback = false;
9998
editor.prevSelection = null;

packages/core/src/react/stores/plate/createPlateStore.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useMemo } from 'react';
22

33
import { atom } from 'jotai';
44
import { useAtomStoreSet, useAtomStoreState, useAtomStoreValue } from 'jotai-x';
@@ -116,15 +116,16 @@ export const usePlateStore = (id?: string) => {
116116
*/
117117
const plateControllerExists = usePlateControllerExists();
118118

119-
const fallbackStore = React.useRef<typeof localStore>(undefined);
119+
const fallbackStore = useMemo(createPlateStore, []);
120+
const fallbackPlateStore = fallbackStore.usePlateStore();
121+
const memorizedFallbackPlateStore = useMemo(
122+
() => fallbackPlateStore,
123+
[fallbackPlateStore]
124+
);
120125

121126
if (!store) {
122127
if (plateControllerExists) {
123-
if (!fallbackStore.current) {
124-
fallbackStore.current =
125-
createPlateStore().usePlateStore() as unknown as typeof localStore;
126-
}
127-
return fallbackStore.current!;
128+
return memorizedFallbackPlateStore;
128129
}
129130

130131
throw new Error(

packages/selection/src/react/BlockSelectionPlugin.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ export const BlockSelectionPlugin = createTPlatePlugin<BlockSelectionConfig>({
205205

206206
return editor.api.blocks<TElement>({
207207
at: [],
208-
match: (n) => !!n.id && selectedIds?.has(n.id),
208+
match: (n) => !!n.id && selectedIds?.has(n.id as string),
209209
});
210210
},
211211
has: (id) => {

packages/selection/src/react/components/BlockSelectionAfterEditable.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export const BlockSelectionAfterEditable: EditableSiblingComponent = () => {
106106
const entry = editor.api.node({
107107
at: [],
108108
block: true,
109-
match: (n) => !!n.id && selectedIds?.has(n.id),
109+
match: (n) => !!n.id && selectedIds?.has(n.id as string),
110110
});
111111

112112
if (entry) {
@@ -124,7 +124,7 @@ export const BlockSelectionAfterEditable: EditableSiblingComponent = () => {
124124
editor.tf.removeNodes({
125125
at: [],
126126
block: true,
127-
match: (n) => !!n.id && selectedIds?.has(n.id),
127+
match: (n) => !!n.id && selectedIds?.has(n.id as string),
128128
});
129129

130130
if (editor.children.length === 0) {
@@ -175,7 +175,7 @@ export const BlockSelectionAfterEditable: EditableSiblingComponent = () => {
175175
if (!editor.api.isReadOnly()) {
176176
editor.tf.removeNodes({
177177
at: [],
178-
match: (n) => selectedIds?.has(n.id),
178+
match: (n) => selectedIds?.has(n.id as string),
179179
});
180180
editor.tf.focus();
181181
}

packages/selection/src/react/hooks/useBlockSelectionNodes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function useBlockSelectionNodes() {
1313
return useMemo(() => {
1414
return editor.api.blocks<TElement>({
1515
at: [],
16-
match: (n) => !!n.id && selectedIds?.has(n.id),
16+
match: (n) => !!n.id && selectedIds?.has(n.id as string),
1717
});
1818
}, [editor, selectedIds]);
1919
}

packages/selection/src/react/hooks/useSelectionArea.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export const useSelectionArea = () => {
7979
const hasAncestor = editor.api.block({
8080
above: true,
8181
at: block[1],
82-
match: (n) => !!n.id && areaRef.current.ids.has(n.id),
82+
match: (n) => !!n.id && areaRef.current.ids.has(n.id as string),
8383
});
8484

8585
if (!hasAncestor) {

0 commit comments

Comments
 (0)