From 32329cf27bc5f6477c16f6e1b9a8a5bfb5ca0d59 Mon Sep 17 00:00:00 2001 From: yyh Date: Fri, 30 Jan 2026 15:06:44 +0800 Subject: [PATCH] perf(skill): stabilize useCallback refs and memoize filtered list Use useRef for batchUpload and emitTreeUpdate to remove unstable dependencies from useCallback, preventing unnecessary memo invalidation on all 16 TemplateCard components. Wrap filtered list in useMemo and replace && conditional with ternary for rendering safety. --- .../start-tab/skill-templates-section.tsx | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/web/app/components/workflow/skill/start-tab/skill-templates-section.tsx b/web/app/components/workflow/skill/start-tab/skill-templates-section.tsx index a48fe3e777..222239f9d0 100644 --- a/web/app/components/workflow/skill/start-tab/skill-templates-section.tsx +++ b/web/app/components/workflow/skill/start-tab/skill-templates-section.tsx @@ -1,7 +1,7 @@ 'use client' import type { SkillTemplateSummary } from './templates/types' -import { memo, useCallback, useState } from 'react' +import { memo, useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' import { useWorkflowStore } from '@/app/components/workflow/store' @@ -24,7 +24,11 @@ const SkillTemplatesSection = () => { const appId = appDetail?.id || '' const storeApi = useWorkflowStore() const batchUpload = useBatchUpload() + const batchUploadRef = useRef(batchUpload) + batchUploadRef.current = batchUpload const emitTreeUpdate = useSkillTreeUpdateEmitter() + const emitTreeUpdateRef = useRef(emitTreeUpdate) + emitTreeUpdateRef.current = emitTreeUpdate const handleUse = useCallback(async (summary: SkillTemplateSummary) => { const entry = SKILL_TEMPLATES.find(e => e.id === summary.id) @@ -39,7 +43,7 @@ const SkillTemplatesSection = () => { const children = await entry.loadContent() const uploadData = await buildUploadDataFromTemplate(summary.name, children) - await batchUpload.mutateAsync({ + await batchUploadRef.current.mutateAsync({ appId, tree: uploadData.tree, files: uploadData.files, @@ -50,7 +54,7 @@ const SkillTemplatesSection = () => { }) storeApi.getState().setUploadStatus('success') - emitTreeUpdate() + emitTreeUpdateRef.current() } catch { storeApi.getState().setUploadStatus('partial_error') @@ -58,9 +62,9 @@ const SkillTemplatesSection = () => { finally { setLoadingId(null) } - }, [appId, batchUpload, storeApi, emitTreeUpdate]) + }, [appId, storeApi]) - const filtered = SKILL_TEMPLATES.filter((entry) => { + const filtered = useMemo(() => SKILL_TEMPLATES.filter((entry) => { if (searchValue) { const q = searchValue.toLowerCase() return entry.name.toLowerCase().includes(q) || entry.description.toLowerCase().includes(q) @@ -68,7 +72,7 @@ const SkillTemplatesSection = () => { if (activeCategory !== 'all') return entry.tags?.some(tag => tag.toLowerCase() === activeCategory.toLowerCase()) return true - }) + }), [searchValue, activeCategory]) return (
@@ -95,9 +99,9 @@ const SkillTemplatesSection = () => { /> ))} - {loadingId && ( -
- )} + {loadingId + ?
+ : null}
) }