PluginBench
Skill
Pass
Audit score 90

component-refactoring

langgenius/dify

Refactor high-complexity React components in Dify frontend using extraction patterns and automated analysis.

What is component-refactoring?

This skill helps decompose complex React components in the Dify frontend codebase when complexity metrics exceed safe thresholds (complexity > 50 or lineCount > 300). Use it when components need code splitting, hook extraction, or complexity reduction, or when analysis tools recommend refactoring before testing.

  • Extract custom hooks to separate state management and business logic from UI components
  • Split monolithic components into focused sub-components organized in subdirectories
  • Simplify deeply nested conditional logic using lookup tables and early returns
  • Extract API calls and data transformation into dedicated service hooks
  • Consolidate multiple modal/dialog states into reusable modal management hooks
  • Analyze component complexity metrics (complexity score, max function complexity, line count) to identify refactoring needs

How to install component-refactoring

npx skills add null --skill component-refactoring
Prerequisites
  • Node.js and pnpm installed in Dify repository
  • Access to `web/` directory with `pnpm` scripts configured
  • Familiarity with React hooks, custom hooks, and component composition
Claude Code
Cursor
Windsurf
Cline

How to use component-refactoring

  1. 1.Run `pnpm analyze-component <path> --json` from `web/` directory to measure current complexity metrics
  2. 2.Review the complexity score (target < 50) and line count (target < 300) to determine if refactoring is needed
  3. 3.Run `pnpm refactor-component <path>` to generate a refactoring prompt with suggested patterns
  4. 4.Apply one or more refactoring patterns: extract custom hooks, split sub-components, simplify conditionals, extract modals, or extract form logic
  5. 5.Use Dify conventions: place hooks in `hooks/` subdirectories, organize sub-components in feature directories, follow existing service patterns in `web/service/`
  6. 6.Re-run `pnpm analyze-component <path> --json` to verify complexity is reduced below 50 and component is ready for testing

Use cases

Good for
  • Refactor a 400-line configuration component with 5+ useState hooks into custom hooks and sub-components
  • Extract complex conditional rendering logic from an app template selector into a lookup-table pattern
  • Split a modal-heavy component managing 6+ modal states into a dedicated modal management hook
  • Reduce a workflow node component from complexity score 75 to below 50 through hook and sub-component extraction
  • Prepare a complex component for testing by reducing its complexity below the 50-point threshold
Who it's for
  • React developers working on Dify frontend codebase
  • Teams refactoring legacy components before adding tests
  • Developers implementing new features with complex state management
  • Code reviewers identifying components that need decomposition

component-refactoring FAQ

When should I use this skill vs. just testing the component?

Use this skill when `pnpm analyze-component --json` shows complexity > 50 or lineCount > 300, when the user explicitly asks for code splitting or hook extraction, or when the tool warns to refactor before testing. Avoid refactoring simple/well-structured components, third-party wrappers, or when the user explicitly wants testing without refactoring.

What complexity score should I target?

Target complexity < 50 (green/yellow zone). Scores 51-75 require refactoring before testing; 76-100 must be refactored. Use the complexity score interpretation table: 0-25 is simple, 26-50 is medium, 51-75 is complex, 76-100 is very complex.

Where should I place extracted custom hooks in Dify?

Follow Dify convention: place hooks in a `hooks/` subdirectory alongside the component or in a dedicated `hooks/` folder within the feature directory. Examples: `web/app/components/app/configuration/hooks/use-advanced-prompt-config.ts` or `web/app/components/workflow/hooks/use-workflow.ts`.

Can I extract API logic into custom hooks during refactoring?

Only extract API logic into custom hooks when it truly orchestrates multiple queries/mutations or shared derived state. Do not create thin passthrough wrappers or use deprecated patterns like `useInvalid` or `useReset`. Refer to existing patterns in `web/service/` for guidance.

How do I know which refactoring pattern to apply?

Use these guidelines: extract custom hooks for complex state/business logic, split sub-components for multiple UI sections, simplify conditionals for deep nesting (>3 levels), extract modals for multiple modal states, and extract form logic for complex validation. Run `pnpm refactor-component <path>` to get specific pattern recommendations.

Full instructions (SKILL.md)

Source of truth, from langgenius/dify.


name: component-refactoring description: Refactor high-complexity React components in Dify frontend. Use when pnpm analyze-component --json shows complexity > 50 or lineCount > 300, when the user asks for code splitting, hook extraction, or complexity reduction, or when pnpm analyze-component warns to refactor before testing; avoid for simple/well-structured components, third-party wrappers, or when the user explicitly wants testing without refactoring.

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 <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)

Complexity Score Interpretation

ScoreLevelAction
0-25🟢 SimpleReady for testing
26-50🟡 MediumConsider minor refactoring
51-75🟠 ComplexRefactor before testing
76-100🔴 Very ComplexMust 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-<feature>.ts.

// ❌ Before: Complex state logic in component
function Configuration() {
  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
function Configuration() {
  const { modelConfig, setModelConfig } = useModelConfig(appId)
  return <div>...</div>
}

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 (
    <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>
  )
}

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 <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])

Pattern 4: Extract API/Data Logic

When: Component directly handles API calls, data transformation, or complex async operations.

Dify Convention:

  • This skill is for component decomposition, not query/mutation design.
  • 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

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<ModalType>(null)
  
  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 <form.Provider>...</form.Provider>
}

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 <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>

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

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 <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

pnpm 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 FeatureRefactoring Action
hasState: true + hasEffects: trueExtract custom hook
hasAPI: trueExtract data/service hook
hasEvents: true (many)Extract event handlers
lineCount > 300Split into sub-components
maxComplexity > 50Simplify conditional logic

Step 4: Execute Incrementally

  1. Extract one piece at a time
  2. Run lint, type-check, and tests after each extraction
  3. Verify functionality before next step
For each extraction:
  ┌────────────────────────────────────────┐
  │ 1. Extract code                        │
  │ 2. Run: pnpm lint:fix                  │
  │ 3. Run: pnpm type-check                │
  │ 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 <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

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/docs/test.md - Testing specification

Related skills

More from langgenius/dify and the wider catalog.