From b014e917400262702e73a4724e89fd55fcee2761 Mon Sep 17 00:00:00 2001 From: Joel Date: Mon, 9 Feb 2026 16:05:53 +0800 Subject: [PATCH] chore: refact tool and filepreveiw context to zustand to reduce rerender --- .../components/base/prompt-editor/index.tsx | 6 +- .../skill/editor/skill-editor/index.tsx | 4 +- .../file-reference-block/component.tsx | 2 +- .../file-reference-block/preview-context.tsx | 65 ++++++++++++-- .../plugins/tool-block/component.tsx | 73 +++++++++------ .../plugins/tool-block/tool-block-context.tsx | 61 +++++++++++-- .../tool-block/tool-group-block-component.tsx | 89 +++++++++++-------- .../plugins/tool-block/tool-picker-block.tsx | 22 +++-- 8 files changed, 234 insertions(+), 88 deletions(-) diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx index 1091c54792..2a9b88fafe 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -333,6 +333,10 @@ const PromptEditorContent: FC = ({ } }, [availableNodes, nodeId, onToolMetadataChange, toolMetadata, workflowVariableBlock?.variables]) + const filePreviewContextValue = React.useMemo(() => ({ + enabled: Boolean(isSupportSandbox), + }), [isSupportSandbox]) + const sandboxPlaceHolder = React.useMemo(() => { if (!isSupportSandbox) return null @@ -387,7 +391,7 @@ const PromptEditorContent: FC = ({ return ( - +
{ + const filePreviewContextValue = React.useMemo(() => ({ enabled: false }), []) + const initialConfig = { namespace: 'skill-editor', nodes: [ @@ -123,7 +125,7 @@ const SkillEditor = ({ return ( - +
const [previewOpen, setPreviewOpen] = useState(false) const [previewStyle, setPreviewStyle] = useState(null) const closeTimerRef = useRef | null>(null) - const { enabled: isPreviewEnabled } = useFilePreviewContext() + const isPreviewEnabled = useFilePreviewContext(context => context.enabled) const { t } = useTranslation() const isInteractive = editor.isEditable() diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/preview-context.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/preview-context.tsx index bb8b0861c8..86a3eab0d3 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/preview-context.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/preview-context.tsx @@ -1,13 +1,62 @@ -import * as React from 'react' +import type { ReactNode } from 'react' +import { createContext, useCallback, useContext, useEffect, useRef } from 'react' +import { useStore } from 'zustand' +import { createStore } from 'zustand/vanilla' -type FilePreviewContextValue = { +export type FilePreviewContextValue = { enabled: boolean } -const FilePreviewContext = React.createContext({ enabled: false }) - -export const FilePreviewContextProvider = FilePreviewContext.Provider - -export const useFilePreviewContext = () => { - return React.useContext(FilePreviewContext) +type FilePreviewStoreState = { + enabled: boolean + setEnabled: (enabled: boolean) => void +} + +const createFilePreviewStore = (enabled: boolean) => createStore(set => ({ + enabled, + setEnabled: nextEnabled => set({ enabled: nextEnabled }), +})) + +type FilePreviewStore = ReturnType + +const defaultFilePreviewStore = createFilePreviewStore(false) + +const FilePreviewStoreContext = createContext(null) + +type FilePreviewContextProviderProps = { + value?: FilePreviewContextValue + children: ReactNode +} + +export const FilePreviewContextProvider = ({ value, children }: FilePreviewContextProviderProps) => { + const storeRef = useRef(null) + if (!storeRef.current) + storeRef.current = createFilePreviewStore(value?.enabled ?? false) + + useEffect(() => { + storeRef.current?.getState().setEnabled(value?.enabled ?? false) + }, [value?.enabled]) + + return ( + + {children} + + ) +} + +// eslint-disable-next-line react-refresh/only-export-components +export const useFilePreviewContext = ( + selector?: (context: FilePreviewContextValue) => T, +) => { + const store = useContext(FilePreviewStoreContext) ?? defaultFilePreviewStore + const selectContext = useCallback( + (state: FilePreviewStoreState) => { + if (selector) + return selector({ enabled: state.enabled }) + return { enabled: state.enabled } as T + }, + [selector], + ) + + return useStore(store, selectContext) } diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx index bcb33fe74c..e7717bf504 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx @@ -9,6 +9,7 @@ import * as React from 'react' import { useEffect, useMemo, useState } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' +import { useShallow } from 'zustand/react/shallow' import AppIcon from '@/app/components/base/app-icon' import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general' import Modal from '@/app/components/base/modal' @@ -127,9 +128,25 @@ const ToolBlockComponent = ({ const { t } = useTranslation() const authBadgeLabel = t('skillEditor.authorizationBadge', { ns: 'workflow' }) const { theme } = useTheme() - const toolBlockContext = useToolBlockContext() - const isUsingExternalMetadata = Boolean(toolBlockContext?.onMetadataChange) - const useModal = Boolean(toolBlockContext?.useModal) + const { + metadata, + onMetadataChange, + useModal, + nodeId: contextNodeId, + nodesOutputVars, + availableNodes, + } = useToolBlockContext( + useShallow(context => ({ + metadata: context?.metadata, + onMetadataChange: context?.onMetadataChange, + useModal: context?.useModal, + nodeId: context?.nodeId, + nodesOutputVars: context?.nodesOutputVars, + availableNodes: context?.availableNodes, + })), + ) + const isUsingExternalMetadata = Boolean(onMetadataChange) + const useModalValue = Boolean(useModal) const [isSettingOpen, setIsSettingOpen] = useState(false) const [toolValue, setToolValue] = useState(null) const [portalContainer, setPortalContainer] = useState(null) @@ -183,14 +200,14 @@ const ToolBlockComponent = ({ const toolConfigFromMetadata = useMemo(() => { if (isUsingExternalMetadata) { - const metadata = toolBlockContext?.metadata as SkillFileMetadata | undefined - return metadata?.tools?.[configId] + const externalMetadata = metadata as SkillFileMetadata | undefined + return externalMetadata?.tools?.[configId] } if (!activeTabId || activeTabId === START_TAB_ID) return undefined - const metadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined - return metadata?.tools?.[configId] - }, [activeTabId, configId, fileMetadata, isUsingExternalMetadata, toolBlockContext?.metadata]) + const resultMetadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined + return resultMetadata?.tools?.[configId] + }, [activeTabId, configId, fileMetadata, isUsingExternalMetadata, metadata]) const isInteractive = editor.isEditable() @@ -281,7 +298,7 @@ const ToolBlockComponent = ({ }, [configuredToolValue, isSettingOpen]) useEffect(() => { - if (useModal) + if (useModalValue) return const containerFromRef = ref.current?.closest('[data-skill-editor-root="true"]') as HTMLElement | null const fallbackContainer = document.querySelector('[data-skill-editor-root="true"]') as HTMLElement | null @@ -289,10 +306,10 @@ const ToolBlockComponent = ({ if (container) // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect setPortalContainer(container) - }, [ref, useModal]) + }, [ref, useModalValue]) useEffect(() => { - if (!isSettingOpen || useModal) + if (!isSettingOpen || useModalValue) return const handleClickOutside = (event: MouseEvent) => { @@ -316,7 +333,7 @@ const ToolBlockComponent = ({ document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) - }, [isSettingOpen, portalContainer, ref, useModal]) + }, [isSettingOpen, portalContainer, ref, useModalValue]) const displayLabel = label || toolMeta?.label || tool const resolvedIcon = (() => { @@ -398,7 +415,7 @@ const ToolBlockComponent = ({ return const credentialId = normalizeCredentialId(nextValue.credential_id) if (isUsingExternalMetadata) { - const metadata = (toolBlockContext?.metadata || {}) as SkillFileMetadata + const externalMetadata = (metadata || {}) as SkillFileMetadata const toolType = currentProvider.type === CollectionType.mcp ? 'mcp' : 'builtin' const buildFields = (value: Record | undefined) => { if (!value) @@ -415,9 +432,9 @@ const ToolBlockComponent = ({ ...buildFields(nextValue.parameters), ] const nextMetadata: SkillFileMetadata = { - ...metadata, + ...externalMetadata, tools: { - ...(metadata.tools || {}), + ...(externalMetadata.tools || {}), [configId]: { type: toolType, configuration: { fields }, @@ -425,12 +442,12 @@ const ToolBlockComponent = ({ }, }, } - toolBlockContext?.onMetadataChange?.(nextMetadata) + onMetadataChange?.(nextMetadata) return } if (!activeTabId || activeTabId === START_TAB_ID) return - const metadata = (fileMetadata.get(activeTabId) || {}) as SkillFileMetadata + const currentMetadata = (fileMetadata.get(activeTabId) || {}) as SkillFileMetadata const toolType = currentProvider.type === CollectionType.mcp ? 'mcp' : 'builtin' const buildFields = (value: Record | undefined) => { if (!value) @@ -447,9 +464,9 @@ const ToolBlockComponent = ({ ...buildFields(nextValue.parameters), ] const nextMetadata: SkillFileMetadata = { - ...metadata, + ...currentMetadata, tools: { - ...(metadata.tools || {}), + ...(currentMetadata.tools || {}), [configId]: { type: toolType, configuration: { fields }, @@ -488,13 +505,13 @@ const ToolBlockComponent = ({ return nextMetadata } if (isUsingExternalMetadata) { - toolBlockContext?.onMetadataChange?.(applyCredential(toolBlockContext?.metadata as SkillFileMetadata | undefined)) + onMetadataChange?.(applyCredential(metadata as SkillFileMetadata | undefined)) return } if (!activeTabId || activeTabId === START_TAB_ID) return - const metadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined - const nextMetadata = applyCredential(metadata) + const currentMetadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined + const nextMetadata = applyCredential(currentMetadata) storeApi.getState().setDraftMetadata(activeTabId, nextMetadata) storeApi.getState().pinTab(activeTabId) } @@ -535,10 +552,10 @@ const ToolBlockComponent = ({ currentTool={currentTool} value={toolValue} onChange={handleToolValueChange} - nodeId={toolBlockContext?.nodeId} - nodesOutputVars={toolBlockContext?.nodesOutputVars} - availableNodes={toolBlockContext?.availableNodes} - enableVariableReference={useModal} + nodeId={contextNodeId} + nodesOutputVars={nodesOutputVars} + availableNodes={availableNodes} + enableVariableReference={useModalValue} /> {readmeEntrance}
@@ -577,7 +594,7 @@ const ToolBlockComponent = ({ )} - {useModal && ( + {useModalValue && ( setIsSettingOpen(false)} @@ -589,7 +606,7 @@ const ToolBlockComponent = ({
)} - {!useModal && portalContainer && isSettingOpen && createPortal( + {!useModalValue && portalContainer && isSettingOpen && createPortal(
onMetadataChange?: (metadata: Record) => void useModal?: boolean @@ -10,8 +13,56 @@ type ToolBlockContextValue = { availableNodes?: Node[] } -const ToolBlockContext = createContext(null) +type ToolBlockStoreState = { + context: ToolBlockContextValue | null + setContext: (context: ToolBlockContextValue | null) => void +} -export const ToolBlockContextProvider = ToolBlockContext.Provider +const createToolBlockStore = (initialContext: ToolBlockContextValue | null) => createStore(set => ({ + context: initialContext, + setContext: context => set({ context }), +})) -export const useToolBlockContext = () => useContext(ToolBlockContext) +type ToolBlockStore = ReturnType + +const defaultToolBlockStore = createToolBlockStore(null) + +const ToolBlockStoreContext = createContext(null) + +type ToolBlockContextProviderProps = { + value?: ToolBlockContextValue | null + children: ReactNode +} + +export const ToolBlockContextProvider = ({ value, children }: ToolBlockContextProviderProps) => { + const storeRef = useRef(null) + if (!storeRef.current) + storeRef.current = createToolBlockStore(value ?? null) + + useEffect(() => { + storeRef.current?.getState().setContext(value ?? null) + }, [value]) + + return ( + + {children} + + ) +} + +// eslint-disable-next-line react-refresh/only-export-components +export const useToolBlockContext = ( + selector?: (context: ToolBlockContextValue | null) => T, +) => { + const store = useContext(ToolBlockStoreContext) ?? defaultToolBlockStore + const selectContext = useCallback( + (state: ToolBlockStoreState) => { + if (selector) + return selector(state.context) + return state.context as T + }, + [selector], + ) + + return useStore(store, selectContext) +} diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx index 4836b5fa99..324ea17e81 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx @@ -9,6 +9,7 @@ import * as React from 'react' import { useCallback, useEffect, useMemo, useState } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' +import { useShallow } from 'zustand/react/shallow' import AppIcon from '@/app/components/base/app-icon' import Modal from '@/app/components/base/modal' import { useSelectOrDelete } from '@/app/components/base/prompt-editor/hooks' @@ -112,9 +113,25 @@ const ToolGroupBlockComponent = ({ const authBadgeLabel = t('skillEditor.authorizationBadge', { ns: 'workflow' }) const language = useGetLanguage() const { theme } = useTheme() - const toolBlockContext = useToolBlockContext() - const isUsingExternalMetadata = Boolean(toolBlockContext?.onMetadataChange) - const useModal = Boolean(toolBlockContext?.useModal) + const { + metadata, + onMetadataChange, + useModal, + nodeId: contextNodeId, + nodesOutputVars, + availableNodes, + } = useToolBlockContext( + useShallow(context => ({ + metadata: context?.metadata, + onMetadataChange: context?.onMetadataChange, + useModal: context?.useModal, + nodeId: context?.nodeId, + nodesOutputVars: context?.nodesOutputVars, + availableNodes: context?.availableNodes, + })), + ) + const isUsingExternalMetadata = Boolean(onMetadataChange) + const useModalValue = Boolean(useModal) const isInteractive = editor.isEditable() const [isSettingOpen, setIsSettingOpen] = useState(false) const [portalContainer, setPortalContainer] = useState(null) @@ -196,22 +213,22 @@ const ToolGroupBlockComponent = ({ if (!activeToolItem) return undefined if (isUsingExternalMetadata) { - const metadata = toolBlockContext?.metadata as SkillFileMetadata | undefined - return metadata?.tools?.[activeToolItem.configId] + const externalMetadata = metadata as SkillFileMetadata | undefined + return externalMetadata?.tools?.[activeToolItem.configId] } if (!activeTabId) return undefined - const metadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined - return metadata?.tools?.[activeToolItem.configId] - }, [activeTabId, activeToolItem, fileMetadata, isUsingExternalMetadata, toolBlockContext?.metadata]) + const resultMetadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined + return resultMetadata?.tools?.[activeToolItem.configId] + }, [activeTabId, activeToolItem, fileMetadata, isUsingExternalMetadata, metadata]) const metadataTools = useMemo(() => { if (isUsingExternalMetadata) - return ((toolBlockContext?.metadata as SkillFileMetadata | undefined)?.tools || {}) as Record + return ((metadata as SkillFileMetadata | undefined)?.tools || {}) as Record if (!activeTabId || activeTabId === START_TAB_ID) return {} return ((fileMetadata.get(activeTabId) as SkillFileMetadata | undefined)?.tools || {}) as Record - }, [activeTabId, fileMetadata, isUsingExternalMetadata, toolBlockContext?.metadata]) + }, [activeTabId, fileMetadata, isUsingExternalMetadata, metadata]) const getVarKindType = (type: FormTypeEnum | string) => { if (type === FormTypeEnum.file || type === FormTypeEnum.files) @@ -348,7 +365,7 @@ const ToolGroupBlockComponent = ({ }, [configuredToolValue, isSettingOpen]) useEffect(() => { - if (useModal) + if (useModalValue) return const containerFromRef = ref.current?.closest('[data-skill-editor-root="true"]') as HTMLElement | null const fallbackContainer = document.querySelector('[data-skill-editor-root="true"]') as HTMLElement | null @@ -356,10 +373,10 @@ const ToolGroupBlockComponent = ({ if (container) // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect setPortalContainer(container) - }, [ref, useModal]) + }, [ref, useModalValue]) useEffect(() => { - if (!isSettingOpen || useModal) + if (!isSettingOpen || useModalValue) return const handleClickOutside = (event: MouseEvent) => { @@ -384,7 +401,7 @@ const ToolGroupBlockComponent = ({ document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) - }, [isSettingOpen, portalContainer, ref, useModal]) + }, [isSettingOpen, portalContainer, ref, useModalValue]) const resolvedEnabledByConfigId = useMemo(() => { const next = { ...enabledByConfigId } @@ -413,9 +430,9 @@ const ToolGroupBlockComponent = ({ setToolValue(nextValue) const credentialId = normalizeCredentialId(nextValue.credential_id) if (isUsingExternalMetadata) { - const metadata = (toolBlockContext?.metadata || {}) as SkillFileMetadata + const externalMetadata = (metadata || {}) as SkillFileMetadata const toolType = currentProvider.type === CollectionType.mcp ? 'mcp' : 'builtin' - const currentToolMetadata = (metadata.tools || {})[activeToolItem.configId] + const currentToolMetadata = (externalMetadata.tools || {})[activeToolItem.configId] const buildFields = (value: Record | undefined) => { if (!value) return [] @@ -431,9 +448,9 @@ const ToolGroupBlockComponent = ({ ...buildFields(nextValue.parameters), ] const nextMetadata: SkillFileMetadata = { - ...metadata, + ...externalMetadata, tools: { - ...(metadata.tools || {}), + ...(externalMetadata.tools || {}), [activeToolItem.configId]: { type: toolType, configuration: { fields }, @@ -442,14 +459,14 @@ const ToolGroupBlockComponent = ({ }, }, } - toolBlockContext?.onMetadataChange?.(nextMetadata) + onMetadataChange?.(nextMetadata) return } if (!activeTabId || activeTabId === START_TAB_ID) return - const metadata = (fileMetadata.get(activeTabId) || {}) as SkillFileMetadata + const currentMetaData = (fileMetadata.get(activeTabId) || {}) as SkillFileMetadata const toolType = currentProvider.type === CollectionType.mcp ? 'mcp' : 'builtin' - const currentToolMetadata = (metadata.tools || {})[activeToolItem.configId] + const currentToolMetadata = (currentMetaData.tools || {})[activeToolItem.configId] const buildFields = (value: Record | undefined) => { if (!value) return [] @@ -465,9 +482,9 @@ const ToolGroupBlockComponent = ({ ...buildFields(nextValue.parameters), ] const nextMetadata: SkillFileMetadata = { - ...metadata, + ...currentMetaData, tools: { - ...(metadata.tools || {}), + ...(currentMetaData.tools || {}), [activeToolItem.configId]: { type: toolType, configuration: { fields }, @@ -502,16 +519,16 @@ const ToolGroupBlockComponent = ({ return nextMetadata } if (isUsingExternalMetadata) { - toolBlockContext?.onMetadataChange?.(applyEnabled(toolBlockContext?.metadata as SkillFileMetadata | undefined)) + onMetadataChange?.(applyEnabled(metadata as SkillFileMetadata | undefined)) return } if (!activeTabId || activeTabId === START_TAB_ID) return - const metadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined - const nextMetadata = applyEnabled(metadata) + const currentMetadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined + const nextMetadata = applyEnabled(currentMetadata) storeApi.getState().setDraftMetadata(activeTabId, nextMetadata) storeApi.getState().pinTab(activeTabId) - }, [activeTabId, currentProvider?.type, fileMetadata, isUsingExternalMetadata, storeApi, toolBlockContext]) + }, [activeTabId, currentProvider?.type, fileMetadata, isUsingExternalMetadata, metadata, onMetadataChange, storeApi]) const renderIcon = () => { if (!resolvedIcon) @@ -611,10 +628,10 @@ const ToolGroupBlockComponent = ({ currentTool={currentTool} value={toolValue} onChange={handleToolValueChange} - nodeId={toolBlockContext?.nodeId} - nodesOutputVars={toolBlockContext?.nodesOutputVars} - availableNodes={toolBlockContext?.availableNodes} - enableVariableReference={useModal} + nodeId={contextNodeId} + nodesOutputVars={nodesOutputVars} + availableNodes={availableNodes} + enableVariableReference={useModalValue} />
) @@ -724,13 +741,13 @@ const ToolGroupBlockComponent = ({ return nextMetadata } if (isUsingExternalMetadata) { - toolBlockContext?.onMetadataChange?.(applyCredential(toolBlockContext?.metadata as SkillFileMetadata | undefined)) + onMetadataChange?.(applyCredential(metadata as SkillFileMetadata | undefined)) return } if (!activeTabId || activeTabId === START_TAB_ID) return - const metadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined - const nextMetadata = applyCredential(metadata) + const currentMetadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined + const nextMetadata = applyCredential(currentMetadata) storeApi.getState().setDraftMetadata(activeTabId, nextMetadata) storeApi.getState().pinTab(activeTabId) }} @@ -841,7 +858,7 @@ const ToolGroupBlockComponent = ({ )} - {useModal && ( + {useModalValue && ( { @@ -856,7 +873,7 @@ const ToolGroupBlockComponent = ({ )} - {!useModal && portalContainer && isSettingOpen && createPortal( + {!useModalValue && portalContainer && isSettingOpen && createPortal(
({ + metadata: context?.metadata, + onMetadataChange: context?.onMetadataChange, + })), + ) + const isUsingExternalMetadata = Boolean(onMetadataChange) const [queryString, setQueryString] = useState('') const canUseAutoByType = useCallback( @@ -133,21 +139,21 @@ const ToolPickerBlock = ({ scope = 'all', enableAutoDefault = false }: ToolPicke }) if (isUsingExternalMetadata) { - const metadata = (toolBlockContext?.metadata || {}) as Record - const nextMetadata = buildNextMetadata(metadata, toolEntries) - toolBlockContext?.onMetadataChange?.(nextMetadata) + const externalMetadata = (metadata || {}) as Record + const nextMetadata = buildNextMetadata(externalMetadata, toolEntries) + onMetadataChange?.(nextMetadata) return } const { activeTabId, fileMetadata, setDraftMetadata, pinTab } = storeApi.getState() if (!activeTabId || activeTabId === START_TAB_ID) return - const metadata = (fileMetadata.get(activeTabId) || {}) as Record - const nextMetadata = buildNextMetadata(metadata, toolEntries) + const currentMetadata = (fileMetadata.get(activeTabId) || {}) as Record + const nextMetadata = buildNextMetadata(currentMetadata, toolEntries) setDraftMetadata(activeTabId, { ...nextMetadata, }) pinTab(activeTabId) - }, [buildNextMetadata, editor, getMatchFromSelection, isUsingExternalMetadata, storeApi, toolBlockContext]) + }, [buildNextMetadata, editor, getMatchFromSelection, isUsingExternalMetadata, metadata, onMetadataChange, storeApi]) const renderMenu = useCallback(( anchorElementRef: React.RefObject,