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.
bash
1cd web
2
3# Generate refactoring prompt
4pnpm refactor-component <path>
5
6# Output refactoring analysis as JSON
7pnpm refactor-component <path> --json
8
9# Generate testing prompt (after refactoring)
10pnpm analyze-component <path>
11
12# Output testing analysis as JSON
13pnpm analyze-component <path> --json
Complexity Analysis
bash
1# Analyze component complexity
2pnpm analyze-component <path> --json
3
4# Key metrics to check:
5# - complexity: normalized score 0-100 (target < 50)
6# - maxComplexity: highest single function complexity
7# - 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
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-<feature>.ts.
typescript
1// ❌ Before: Complex state logic in component
2const Configuration: FC = () => {
3 const [modelConfig, setModelConfig] = useState<ModelConfig>(...)
4 const [datasetConfigs, setDatasetConfigs] = useState<DatasetConfigs>(...)
5 const [completionParams, setCompletionParams] = useState<FormValue>({})
6
7 // 50+ lines of state management logic...
8
9 return <div>...</div>
10}
11
12// ✅ After: Extract to custom hook
13// hooks/use-model-config.ts
14export const useModelConfig = (appId: string) => {
15 const [modelConfig, setModelConfig] = useState<ModelConfig>(...)
16 const [completionParams, setCompletionParams] = useState<FormValue>({})
17
18 // Related state management logic here
19
20 return { modelConfig, setModelConfig, completionParams, setCompletionParams }
21}
22
23// Component becomes cleaner
24const Configuration: FC = () => {
25 const { modelConfig, setModelConfig } = useModelConfig(appId)
26 return <div>...</div>
27}
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
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.
typescript
1// ❌ Before: Monolithic JSX with multiple sections
2const AppInfo = () => {
3 return (
4 <div>
5 {/* 100 lines of header UI */}
6 {/* 100 lines of operations UI */}
7 {/* 100 lines of modals */}
8 </div>
9 )
10}
11
12// ✅ After: Split into focused components
13// app-info/
14// ├── index.tsx (orchestration only)
15// ├── app-header.tsx (header UI)
16// ├── app-operations.tsx (operations UI)
17// └── app-modals.tsx (modal management)
18
19const AppInfo = () => {
20 const { showModal, setShowModal } = useAppInfoModals()
21
22 return (
23 <div>
24 <AppHeader appDetail={appDetail} />
25 <AppOperations onAction={handleAction} />
26 <AppModals show={showModal} onClose={() => setShowModal(null)} />
27 </div>
28 )
29}
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.
typescript
1// ❌ Before: Deeply nested conditionals
2const Template = useMemo(() => {
3 if (appDetail?.mode === AppModeEnum.CHAT) {
4 switch (locale) {
5 case LanguagesSupported[1]:
6 return <TemplateChatZh />
7 case LanguagesSupported[7]:
8 return <TemplateChatJa />
9 default:
10 return <TemplateChatEn />
11 }
12 }
13 if (appDetail?.mode === AppModeEnum.ADVANCED_CHAT) {
14 // Another 15 lines...
15 }
16 // More conditions...
17}, [appDetail, locale])
18
19// ✅ After: Use lookup tables + early returns
20const TEMPLATE_MAP = {
21 [AppModeEnum.CHAT]: {
22 [LanguagesSupported[1]]: TemplateChatZh,
23 [LanguagesSupported[7]]: TemplateChatJa,
24 default: TemplateChatEn,
25 },
26 [AppModeEnum.ADVANCED_CHAT]: {
27 [LanguagesSupported[1]]: TemplateAdvancedChatZh,
28 // ...
29 },
30}
31
32const Template = useMemo(() => {
33 const modeTemplates = TEMPLATE_MAP[appDetail?.mode]
34 if (!modeTemplates) return null
35
36 const TemplateComponent = modeTemplates[locale] || modeTemplates.default
37 return <TemplateComponent appDetail={appDetail} />
38}, [appDetail, locale])
When: Component directly handles API calls, data transformation, or complex async operations.
Dify Convention:
- This skill is for component decomposition, not query/mutation design.
- When refactoring data fetching, follow
web/AGENTS.md.
- Use
frontend-query-mutation for contracts, query shape, data-fetching wrappers, query/mutation call-site patterns, conditional queries, invalidation, and mutation error handling.
- Do not introduce deprecated
useInvalid / useReset.
- Do not add thin passthrough
useQuery wrappers during refactoring; only extract a custom hook when it truly orchestrates multiple queries/mutations or shared derived state.
Dify Examples:
web/service/use-workflow.ts
web/service/use-common.ts
web/service/knowledge/use-dataset.ts
web/service/knowledge/use-document.ts
When: Component manages multiple modals with complex open/close states.
Dify Convention: Modals should be extracted with their state management.
typescript
1// ❌ Before: Multiple modal states in component
2const AppInfo = () => {
3 const [showEditModal, setShowEditModal] = useState(false)
4 const [showDuplicateModal, setShowDuplicateModal] = useState(false)
5 const [showConfirmDelete, setShowConfirmDelete] = useState(false)
6 const [showSwitchModal, setShowSwitchModal] = useState(false)
7 const [showImportDSLModal, setShowImportDSLModal] = useState(false)
8 // 5+ more modal states...
9}
10
11// ✅ After: Extract to modal management hook
12type ModalType = 'edit' | 'duplicate' | 'delete' | 'switch' | 'import' | null
13
14const useAppInfoModals = () => {
15 const [activeModal, setActiveModal] = useState<ModalType>(null)
16
17 const openModal = useCallback((type: ModalType) => setActiveModal(type), [])
18 const closeModal = useCallback(() => setActiveModal(null), [])
19
20 return {
21 activeModal,
22 openModal,
23 closeModal,
24 isOpen: (type: ModalType) => activeModal === type,
25 }
26}
When: Complex form validation, submission handling, or field transformation.
Dify Convention: Use @tanstack/react-form patterns from web/app/components/base/form/.
typescript
1// ✅ Use existing form infrastructure
2import { useAppForm } from '@/app/components/base/form'
3
4const ConfigForm = () => {
5 const form = useAppForm({
6 defaultValues: { name: '', description: '' },
7 onSubmit: handleSubmit,
8 })
9
10 return <form.Provider>...</form.Provider>
11}
Dify-Specific Refactoring Guidelines
When: Component provides complex context values with multiple states.
typescript
1// ❌ Before: Large context value object
2const value = {
3 appId, isAPIKeySet, isTrailFinished, mode, modelModeType,
4 promptMode, isAdvancedMode, isAgent, isOpenAI, isFunctionCall,
5 // 50+ more properties...
6}
7return <ConfigContext.Provider value={value}>...</ConfigContext.Provider>
8
9// ✅ After: Split into domain-specific contexts
10<ModelConfigProvider value={modelConfigValue}>
11 <DatasetConfigProvider value={datasetConfigValue}>
12 <UIConfigProvider value={uiConfigValue}>
13 {children}
14 </UIConfigProvider>
15 </DatasetConfigProvider>
16</ModelConfigProvider>
Dify Reference: web/context/ directory structure
2. 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/<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
3. 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
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
bash
1pnpm refactor-component <path>
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
bash
1pnpm analyze-component <path> --json
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:
bash
1# Re-run refactor command to verify improvements
2pnpm refactor-component <path>
3
4# If complexity < 25 and lines < 200, you'll see:
5# ✅ COMPONENT IS WELL-STRUCTURED
6
7# For detailed metrics:
8pnpm analyze-component <path> --json
9
10# Target metrics:
11# - complexity < 50
12# - lineCount < 300
13# - maxComplexity < 30
Common Mistakes to Avoid
❌ Over-Engineering
typescript
1// ❌ Too many tiny hooks
2const useButtonText = () => useState('Click')
3const useButtonDisabled = () => useState(false)
4const useButtonLoading = () => useState(false)
5
6// ✅ Cohesive hook with related state
7const useButtonState = () => {
8 const [text, setText] = useState('Click')
9 const [disabled, setDisabled] = useState(false)
10 const [loading, setLoading] = useState(false)
11 return { text, setText, disabled, setDisabled, loading, setLoading }
12}
❌ 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/
frontend-testing - For testing refactored components
web/docs/test.md - Testing specification