Refactor high-complexity React components in the Dify frontend codebase with the patterns and workflow below. > **Complexity Threshold**: Components with complexity > 50 (measured by `pnpm analyze-component`) should be refactored before testing. Use paths relative to `web/` (e.g., `app/components/...`). Use `refactor-component` for refactoring prompts and `analyze-component` for testing prompts and metrics.
Complexity Threshold: Components with complexity > 50 (measured bypnpm analyze-component) should be refactored before testing.
web/)web/ (e.g., app/components/...). Use refactor-component for refactoring prompts and analyze-component for testing prompts and metrics.cd web # Generate refactoring prompt pnpm refactor-component <path> # Output refactoring analysis as JSON pnpm refactor-component <path> --json # Generate testing prompt (after refactoring) pnpm analyze-component <path> # Output testing analysis as JSON pnpm analyze-component <path> --json `### Complexity Analysis` # Analyze component complexity pnpm analyze-component <path> --json # Key metrics to check: # - complexity: normalized score 0-100 (target < 50) # - maxComplexity: highest single function complexity # - lineCount: total lines (target < 300)
useState/useEffect, or business logic mixed with UI.hooks/ subdirectory or alongside the component as use-<feature>.ts.// ❌ Before: Complex state logic in component const Configuration: FC = () => { const [modelConfig, setModelConfig] = useState<ModelConfig>(...) const [datasetConfigs, setDatasetConfigs] = useState<DatasetConfigs>(...) const [completionParams, setCompletionParams] = useState<FormValue>({}) // 50+ lines of state management logic... return <div>...</div> } // ✅ After: Extract to custom hook // hooks/use-model-config.ts export const useModelConfig = (appId: string) => { const [modelConfig, setModelConfig] = useState<ModelConfig>(...) const [completionParams, setCompletionParams] = useState<FormValue>({}) // Related state management logic here return { modelConfig, setModelConfig, completionParams, setCompletionParams } } // Component becomes cleaner const Configuration: FC = () => { const { modelConfig, setModelConfig } = useModelConfig(appId) return <div>...</div> }
web/app/components/app/configuration/hooks/use-advanced-prompt-config.tsweb/app/components/app/configuration/debug/hooks.tsxweb/app/components/workflow/hooks/use-workflow.ts// ❌ Before: Monolithic JSX with multiple sections const AppInfo = () => { return ( <div> {/* 100 lines of header UI */} {/* 100 lines of operations UI */} {/* 100 lines of modals */} </div> ) } // ✅ After: Split into focused components // app-info/ // ├── index.tsx (orchestration only) // ├── app-header.tsx (header UI) // ├── app-operations.tsx (operations UI) // └── app-modals.tsx (modal management) const AppInfo = () => { const { showModal, setShowModal } = useAppInfoModals() return ( <div> <AppHeader appDetail={appDetail} /> <AppOperations onAction={handleAction} /> <AppModals show={showModal} onClose={() => setShowModal(null)} /> </div> ) }
web/app/components/app/configuration/ directory structureweb/app/components/workflow/nodes/ per-node organizationif/else chains.// ❌ Before: Deeply nested conditionals const Template = useMemo(() => { if (appDetail?.mode === AppModeEnum.CHAT) { switch (locale) { case LanguagesSupported[1]: return <TemplateChatZh /> case LanguagesSupported[7]: return <TemplateChatJa /> default: return <TemplateChatEn /> } } if (appDetail?.mode === AppModeEnum.ADVANCED_CHAT) { // Another 15 lines... } // More conditions... }, [appDetail, locale]) // ✅ After: Use lookup tables + early returns const TEMPLATE_MAP = { [AppModeEnum.CHAT]: { [LanguagesSupported[1]]: TemplateChatZh, [LanguagesSupported[7]]: TemplateChatJa, default: TemplateChatEn, }, [AppModeEnum.ADVANCED_CHAT]: { [LanguagesSupported[1]]: TemplateAdvancedChatZh, // ... }, } const Template = useMemo(() => { const modeTemplates = TEMPLATE_MAP[appDetail?.mode] if (!modeTemplates) return null const TemplateComponent = modeTemplates[locale] || modeTemplates.default return <TemplateComponent appDetail={appDetail} /> }, [appDetail, locale])
@tanstack/react-query hooks from web/service/use-*.ts or create custom data hooks.// ❌ Before: API logic in component const MCPServiceCard = () => { const [basicAppConfig, setBasicAppConfig] = useState({}) useEffect(() => { if (isBasicApp && appId) { (async () => { const res = await fetchAppDetail({ url: '/apps', id: appId }) setBasicAppConfig(res?.model_config || {}) })() } }, [appId, isBasicApp]) // More API-related logic... } // ✅ After: Extract to data hook using React Query // use-app-config.ts import { useQuery } from '@tanstack/react-query' import { get } from '@/service/base' const NAME_SPACE = 'appConfig' export const useAppConfig = (appId: string, isBasicApp: boolean) => { return useQuery({ enabled: isBasicApp && !!appId, queryKey: [NAME_SPACE, 'detail', appId], queryFn: () => get<AppDetailResponse>(`/apps/${appId}`), select: data => data?.model_config || {}, }) } // Component becomes cleaner const MCPServiceCard = () => { const { data: config, isLoading } = useAppConfig(appId, isBasicApp) // UI only }
NAME_SPACE for query key organizationenabled option for conditional fetchingselect for data transformationuseInvalidXxxweb/service/use-workflow.tsweb/service/use-common.tsweb/service/knowledge/use-dataset.tsweb/service/knowledge/use-document.ts// ❌ Before: Multiple modal states in component const AppInfo = () => { const [showEditModal, setShowEditModal] = useState(false) const [showDuplicateModal, setShowDuplicateModal] = useState(false) const [showConfirmDelete, setShowConfirmDelete] = useState(false) const [showSwitchModal, setShowSwitchModal] = useState(false) const [showImportDSLModal, setShowImportDSLModal] = useState(false) // 5+ more modal states... } // ✅ After: Extract to modal management hook type ModalType = 'edit' | 'duplicate' | 'delete' | 'switch' | 'import' | null const useAppInfoModals = () => { const [activeModal, setActiveModal] = useState<ModalType>(null) const openModal = useCallback((type: ModalType) => setActiveModal(type), []) const closeModal = useCallback(() => setActiveModal(null), []) return { activeModal, openModal, closeModal, isOpen: (type: ModalType) => activeModal === type, } }
@tanstack/react-form patterns from web/app/components/base/form/.// ✅ Use existing form infrastructure import { useAppForm } from '@/app/components/base/form' const ConfigForm = () => { const form = useAppForm({ defaultValues: { name: '', description: '' }, onSubmit: handleSubmit, }) return <form.Provider>...</form.Provider> }
// ❌ Before: Large context value object const value = { appId, isAPIKeySet, isTrailFinished, mode, modelModeType, promptMode, isAdvancedMode, isAgent, isOpenAI, isFunctionCall, // 50+ more properties... } return <ConfigContext.Provider value={value}>...</ConfigContext.Provider> // ✅ After: Split into domain-specific contexts <ModelConfigProvider value={modelConfigValue}> <DatasetConfigProvider value={datasetConfigValue}> <UIConfigProvider value={uiConfigValue}> {children} </UIConfigProvider> </DatasetConfigProvider> </ModelConfigProvider>
web/context/ directory structureweb/app/components/workflow/nodes/).use-interactions.ts_base components for common patternsnodes/<node-type>/ ├── index.tsx # Node registration ├── node.tsx # Node visual component ├── panel.tsx # Configuration panel ├── use-interactions.ts # Node-specific hooks └── types.ts # Type definitions
web/app/components/app/configuration/web/app/components/tools/).web/service/use-tools.tspnpm refactor-component <path>pnpm analyze-component <path> --jsonhasState: true + hasEffects: truehasAPI: truehasEvents: true (many)lineCount > 300maxComplexity > 50For each extraction: ┌────────────────────────────────────────┐ │ 1. Extract code │ │ 2. Run: pnpm lint:fix │ │ 3. Run: pnpm type-check:tsgo │ │ 4. Run: pnpm test │ │ 5. Test functionality manually │ │ 6. PASS? → Next extraction │ │ FAIL? → Fix before continuing │ └────────────────────────────────────────┘
# Re-run refactor command to verify improvements pnpm refactor-component <path> # If complexity < 25 and lines < 200, you'll see: # ✅ COMPONENT IS WELL-STRUCTURED # For detailed metrics: pnpm analyze-component <path> --json # Target metrics: # - complexity < 50 # - lineCount < 300 # - maxComplexity < 30
// ❌ Too many tiny hooks const useButtonText = () => useState('Click') const useButtonDisabled = () => useState(false) const useButtonLoading = () => useState(false) // ✅ Cohesive hook with related state const useButtonState = () => { const [text, setText] = useState('Click') const [disabled, setDisabled] = useState(false) const [loading, setLoading] = useState(false) return { text, setText, disabled, setDisabled, loading, setLoading } }
web/app/components/app/configuration/hooks/web/app/components/app/configuration/web/service/use-*.tsweb/app/components/workflow/hooks/web/app/components/base/form/frontend-testing - For testing refactored componentsweb/docs/test.md - Testing specification