From 6f9e844cfb535328f25a77b507ae2c778758bb0e Mon Sep 17 00:00:00 2001 From: zbeyens Date: Wed, 27 Nov 2024 10:20:02 +0100 Subject: [PATCH 1/3] perf --- .../react/components/EditorMethodsEffect.ts | 4 +++- .../core/src/react/editor/usePlateEditor.ts | 6 ++++-- .../core/src/react/hooks/useEditableProps.ts | 5 +++-- packages/react-utils/src/index.ts | 2 ++ packages/react-utils/src/useEffectOnce.ts | 20 ++++++++++++++++++ packages/react-utils/src/useMemoOnce.ts | 21 +++++++++++++++++++ 6 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 packages/react-utils/src/useEffectOnce.ts create mode 100644 packages/react-utils/src/useMemoOnce.ts diff --git a/packages/core/src/react/components/EditorMethodsEffect.ts b/packages/core/src/react/components/EditorMethodsEffect.ts index a25a64dbf1..4acec20378 100644 --- a/packages/core/src/react/components/EditorMethodsEffect.ts +++ b/packages/core/src/react/components/EditorMethodsEffect.ts @@ -1,5 +1,7 @@ import React from 'react'; +import { useMemoOnce } from '@udecode/react-utils'; + import { EXPOSED_STORE_KEYS, useEditorRef, @@ -18,7 +20,7 @@ export const EditorMethodsEffect = ({ id }: { id?: string }) => { EXPOSED_STORE_KEYS.map((key) => [key, plateStore.set[key]()]) ) as any; - const memorizedStoreSetters = React.useMemo( + const memorizedStoreSetters = useMemoOnce( () => storeSetters, // eslint-disable-next-line react-hooks/exhaustive-deps [] diff --git a/packages/core/src/react/editor/usePlateEditor.ts b/packages/core/src/react/editor/usePlateEditor.ts index 1c45b508bc..9c7d5d9ba2 100644 --- a/packages/core/src/react/editor/usePlateEditor.ts +++ b/packages/core/src/react/editor/usePlateEditor.ts @@ -1,7 +1,9 @@ -import React from 'react'; +import type React from 'react'; import type { Value } from '@udecode/slate'; +import { useMemoOnce } from '@udecode/react-utils'; + import type { AnyPluginConfig } from '../../lib'; import { @@ -34,7 +36,7 @@ export function usePlateEditor< : TEnabled extends true | undefined ? TPlateEditor : TPlateEditor | null { - return React.useMemo( + return useMemoOnce( (): any => { if (options.enabled === false) return null; diff --git a/packages/core/src/react/hooks/useEditableProps.ts b/packages/core/src/react/hooks/useEditableProps.ts index 270b8fe4bf..79a8647931 100644 --- a/packages/core/src/react/hooks/useEditableProps.ts +++ b/packages/core/src/react/hooks/useEditableProps.ts @@ -2,6 +2,7 @@ import React from 'react'; import type { TEditableProps } from '@udecode/slate-react'; +import { useMemoOnce } from '@udecode/react-utils'; import clsx from 'clsx'; import omit from 'lodash/omit.js'; import { useDeepCompareMemo } from 'use-deep-compare'; @@ -31,11 +32,11 @@ export const useEditableProps = ({ const storeRenderLeaf = selectors.renderLeaf(); const storeRenderElement = selectors.renderElement(); - const decorateMemo = React.useMemo(() => { + const decorateMemo = useMemoOnce(() => { return pipeDecorate(editor, storeDecorate ?? editableProps?.decorate); }, [editableProps?.decorate, editor, storeDecorate]); - const decorate: typeof decorateMemo = React.useMemo(() => { + const decorate: typeof decorateMemo = useMemoOnce(() => { if (!versionDecorate || !decorateMemo) return; return (entry) => decorateMemo(entry); diff --git a/packages/react-utils/src/index.ts b/packages/react-utils/src/index.ts index e6174df0a5..fd19a8a4ba 100644 --- a/packages/react-utils/src/index.ts +++ b/packages/react-utils/src/index.ts @@ -11,7 +11,9 @@ export * from './createPrimitiveComponent'; export * from './createPrimitiveElement'; export * from './createSlotComponent'; export * from './useComposedRef'; +export * from './useEffectOnce'; export * from './useIsomorphicLayoutEffect'; +export * from './useMemoOnce'; export * from './useOnClickOutside'; export * from './useStableMemo'; export * from './withProviders'; diff --git a/packages/react-utils/src/useEffectOnce.ts b/packages/react-utils/src/useEffectOnce.ts new file mode 100644 index 0000000000..9392f1bd83 --- /dev/null +++ b/packages/react-utils/src/useEffectOnce.ts @@ -0,0 +1,20 @@ +import React from 'react'; + +export function useEffectOnce( + effect: React.EffectCallback, + deps: React.DependencyList +) { + const initialized = React.useRef(false); + const prevDepsRef = React.useRef(deps); + + React.useEffect(() => { + const depsChanged = deps.some((dep, i) => dep !== prevDepsRef.current[i]); + + if (!initialized.current || depsChanged) { + initialized.current = true; + prevDepsRef.current = deps; + effect(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, deps); +} diff --git a/packages/react-utils/src/useMemoOnce.ts b/packages/react-utils/src/useMemoOnce.ts new file mode 100644 index 0000000000..6cf87c7bf0 --- /dev/null +++ b/packages/react-utils/src/useMemoOnce.ts @@ -0,0 +1,21 @@ +import React from 'react'; + +export function useMemoOnce( + factory: () => T, + deps: React.DependencyList +): T { + const initialized = React.useRef(false); + const prevDepsRef = React.useRef(deps); + const memoizedValueRef = React.useRef(); + + if ( + !initialized.current || + deps.some((dep, i) => dep !== prevDepsRef.current[i]) + ) { + initialized.current = true; + prevDepsRef.current = deps; + memoizedValueRef.current = factory(); + } + + return memoizedValueRef.current!; +} From e1845c3a60ce6a012c65dd1de64723fd64d2e058 Mon Sep 17 00:00:00 2001 From: zbeyens Date: Wed, 27 Nov 2024 10:20:11 +0100 Subject: [PATCH 2/3] docs --- .changeset/old-olives-scream.md | 5 +++++ .changeset/thirty-cats-dress.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/old-olives-scream.md create mode 100644 .changeset/thirty-cats-dress.md diff --git a/.changeset/old-olives-scream.md b/.changeset/old-olives-scream.md new file mode 100644 index 0000000000..235768d0fe --- /dev/null +++ b/.changeset/old-olives-scream.md @@ -0,0 +1,5 @@ +--- +'@udecode/plate-core': patch +--- + +Use useMemoOnce for decorate, usePlateEditor diff --git a/.changeset/thirty-cats-dress.md b/.changeset/thirty-cats-dress.md new file mode 100644 index 0000000000..2b68160d06 --- /dev/null +++ b/.changeset/thirty-cats-dress.md @@ -0,0 +1,5 @@ +--- +'@udecode/react-utils': patch +--- + +Add useEffectOnce, useMemoOnce From 3f38c6b23d65525b7969798827ae2a44a22b2bfb Mon Sep 17 00:00:00 2001 From: zbeyens Date: Wed, 27 Nov 2024 10:20:17 +0100 Subject: [PATCH 3/3] docs --- apps/www/content/docs/multi-select.mdx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/www/content/docs/multi-select.mdx b/apps/www/content/docs/multi-select.mdx index d726fbff18..1ccd332961 100644 --- a/apps/www/content/docs/multi-select.mdx +++ b/apps/www/content/docs/multi-select.mdx @@ -15,16 +15,15 @@ Unlike traditional input-based multi-selects, this component is built on top of - Full history support (undo/redo) - Native cursor navigation between and within tags -- Text selection across multiple tags -- Copy/paste support for tags +- Select one to many tags +- Copy/paste tags - Drag and drop to reorder tags -- Read-only mode support +- Read-only mode - Duplicate tags prevention -- Support creating new tags if not in the list -- Automatic search text cleanup -- Automatic whitespace trimming -- Headless components for full styling control -- Fuzzy search with keyboard navigation and [cmdk](https://github.com/pacocoursey/cmdk) +- Create new tags, case insensitive +- Search text cleanup +- Whitespace trimming +- Fuzzy search with [cmdk](https://github.com/pacocoursey/cmdk)