Dify Component Refactoring Skill
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.
Quick Reference Commands (run from web/)
Use paths relative to 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
Output refactoring analysis as JSON
pnpm refactor-component
Generate testing prompt (after refactoring)
pnpm analyze-component
Output testing analysis as JSON
pnpm analyze-component
Complexity Analysis
Analyze component complexity
pnpm analyze-component
Key metrics to check:
- complexity: normalized score 0-100 (target < 50)
- maxComplexity: highest single function complexity
- lineCount: total lines (target < 300)
Complexity Score Interpretation Score Level Action 0-25 🟢 Simple Ready for testing 26-50 🟡 Medium Consider minor refactoring 51-75 🟠 Complex Refactor before testing 76-100 🔴 Very Complex Must refactor Core Refactoring Patterns Pattern 1: Extract Custom Hooks
When: Component has complex state management, multiple useState/useEffect, or business logic mixed with UI.
Dify Convention: Place hooks in a hooks/ subdirectory or alongside the component as use-
// ❌ Before: Complex state logic in component
const Configuration: FC = () => {
const [modelConfig, setModelConfig] = useState
// 50+ lines of state management logic...
return
// ✅ After: Extract to custom hook
// hooks/use-model-config.ts
export const useModelConfig = (appId: string) => {
const [modelConfig, setModelConfig] = useState
// Related state management logic here
return { modelConfig, setModelConfig, completionParams, setCompletionParams } }
// Component becomes cleaner const Configuration: FC = () => { const { modelConfig, setModelConfig } = useModelConfig(appId) return
Dify Examples:
web/app/components/app/configuration/hooks/use-advanced-prompt-config.ts web/app/components/app/configuration/debug/hooks.tsx web/app/components/workflow/hooks/use-workflow.ts Pattern 2: Extract Sub-Components
When: Single component has multiple UI sections, conditional rendering blocks, or repeated patterns.
Dify Convention: Place sub-components in subdirectories or as separate files in the same directory.
// ❌ Before: Monolithic JSX with multiple sections const AppInfo = () => { return (
// ✅ 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 (
Dify Examples:
web/app/components/app/configuration/ directory structure web/app/components/workflow/nodes/ per-node organization Pattern 3: Simplify Conditional Logic
When: Deep nesting (> 3 levels), complex ternaries, or multiple if/else chains.
// ❌ Before: Deeply nested conditionals
const Template = useMemo(() => {
if (appDetail?.mode === AppModeEnum.CHAT) {
switch (locale) {
case LanguagesSupported[1]:
return
// ✅ After: Use lookup tables + early returns const TEMPLATE_MAP = {
[LanguagesSupported[1]]: TemplateChatZh,
[LanguagesSupported[7]]: TemplateChatJa,
default: TemplateChatEn,
},
[LanguagesSupported[1]]: TemplateAdvancedChatZh,
// ...
}, }
const Template = useMemo(() => { const modeTemplates = TEMPLATE_MAP[appDetail?.mode] if (!modeTemplates) return null
const TemplateComponent = modeTemplates[locale] || modeTemplates.default
return
Pattern 4: Extract API/Data Logic
When: Component directly handles API calls, data transformation, or complex async operations.
Dify Convention: Use @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/apps/${appId}),
select: data => data?.model_config || {},
})
}
// Component becomes cleaner const MCPServiceCard = () => { const { data: config, isLoading } = useAppConfig(appId, isBasicApp) // UI only }
React Query Best Practices in Dify:
Define NAME_SPACE for query key organization Use enabled option for conditional fetching Use select for data transformation Export invalidation hooks: useInvalidXxx
Dify Examples:
web/service/use-workflow.ts web/service/use-common.ts web/service/knowledge/use-dataset.ts web/service/knowledge/use-document.ts Pattern 5: Extract Modal/Dialog Management
When: Component manages multiple modals with complex open/close states.
Dify Convention: Modals should be extracted with their state management.
// ❌ 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
const openModal = useCallback((type: ModalType) => setActiveModal(type), []) const closeModal = useCallback(() => setActiveModal(null), [])
return { activeModal, openModal, closeModal, isOpen: (type: ModalType) => activeModal === type, } }
Pattern 6: Extract Form Logic
When: Complex form validation, submission handling, or field transformation.
Dify Convention: Use @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
Dify-Specific Refactoring Guidelines 1. Context Provider Extraction
When: Component provides complex context values with multiple states.
// ❌ Before: Large context value object
const value = {
appId, isAPIKeySet, isTrailFinished, mode, modelModeType,
promptMode, isAdvancedMode, isAgent, isOpenAI, isFunctionCall,
// 50+ more properties...
}
return
// ✅ After: Split into domain-specific contexts
Dify Reference: web/context/ directory structure
- Workflow Node Components
When: Refactoring workflow node components (web/app/components/workflow/nodes/).
Conventions:
Keep node logic in use-interactions.ts
Extract panel UI to separate files
Use _base components for common patterns
nodes/
- Configuration Components
When: Refactoring app configuration components.
Conventions:
Separate config sections into subdirectories Use existing patterns from web/app/components/app/configuration/ Keep feature toggles in dedicated components 4. Tool/Plugin Components
When: Refactoring tool-related components (web/app/components/tools/).
Conventions:
Follow existing modal patterns
Use service hooks from web/service/use-tools.ts
Keep provider-specific logic isolated
Refactoring Workflow
Step 1: Generate Refactoring Prompt
pnpm refactor-component
This command will:
Analyze component complexity and features
Identify specific refactoring actions needed
Generate a prompt for AI assistant (auto-copied to clipboard on macOS)
Provide detailed requirements based on detected patterns
Step 2: Analyze Details
pnpm analyze-component
Identify:
Total complexity score Max function complexity Line count Features detected (state, effects, API, etc.) Step 3: Plan
Create a refactoring plan based on detected features:
Detected Feature Refactoring Action hasState: true + hasEffects: true Extract custom hook hasAPI: true Extract data/service hook hasEvents: true (many) Extract event handlers lineCount > 300 Split into sub-components maxComplexity > 50 Simplify conditional logic Step 4: Execute Incrementally Extract one piece at a time Run lint, type-check, and tests after each extraction Verify functionality before next step For 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 │ └────────────────────────────────────────┘
Step 5: Verify
After refactoring:
Re-run refactor command to verify improvements
pnpm refactor-component
If complexity < 25 and lines < 200, you'll see:
✅ COMPONENT IS WELL-STRUCTURED
For detailed metrics:
pnpm analyze-component
Target metrics:
- complexity < 50
- lineCount < 300
- maxComplexity < 30
Common Mistakes to Avoid ❌ Over-Engineering // ❌ 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 } }
❌ Breaking Existing Patterns Follow existing directory structures Maintain naming conventions Preserve export patterns for compatibility ❌ Premature Abstraction Only extract when there's clear complexity benefit Don't create abstractions for single-use code Keep refactored code in the same domain area References Dify Codebase Examples Hook extraction: web/app/components/app/configuration/hooks/ Component splitting: web/app/components/app/configuration/ Service hooks: web/service/use-*.ts Workflow patterns: web/app/components/workflow/hooks/ Form patterns: web/app/components/base/form/ Related Skills frontend-testing - For testing refactored components web/testing/testing.md - Testing specification