All skills
Skillintermediate

Component Splitting Patterns

This document provides detailed guidance on splitting large components into smaller, focused components in Dify.

Claude Code Knowledge Pack7/10/2026

Overview

Component Splitting Patterns

This document provides detailed guidance on splitting large components into smaller, focused components in Dify.

When to Split Components

Split a component when you identify:

  1. Multiple UI sections - Distinct visual areas with minimal coupling that can be composed independently
  2. Conditional rendering blocks - Large {condition && } blocks
  3. Repeated patterns - Similar UI structures used multiple times
  4. 300+ lines - Component exceeds manageable size
  5. Modal clusters - Multiple modals rendered in one component

Splitting Strategies

Strategy 1: Section-Based Splitting

Identify visual sections and extract each as a component.

// ❌ Before: Monolithic component (500+ lines)
const ConfigurationPage = () => {
  return (
    <div>
      
      <div className="header">
        <h1>{t('configuration.title')}</h1>
        <div className="actions">
          {isAdvancedMode && Advanced}
          
          
        </div>
      </div>
      
      
      <div className="config">
        
      </div>
      
      
      <div className="debug">
        
      </div>
      
      
      {showSelectDataSet && }
      {showHistoryModal && }
      {showUseGPT4Confirm && }
    </div>
  )
}

// ✅ After: Split into focused components
// configuration/
//   ├── index.tsx              (orchestration)
//   ├── configuration-header.tsx
//   ├── configuration-content.tsx
//   ├── configuration-debug.tsx
//   └── configuration-modals.tsx

// configuration-header.tsx
interface ConfigurationHeaderProps {
  isAdvancedMode: boolean
  onPublish: () => void
}

const ConfigurationHeader: FC = ({
  isAdvancedMode,
  onPublish,
}) => {
  const { t } = useTranslation()
  
  return (
    <div className="header">
      <h1>{t('configuration.title')}</h1>
      <div className="actions">
        {isAdvancedMode && Advanced}
        
        
      </div>
    </div>
  )
}

// index.tsx (orchestration only)
const ConfigurationPage = () => {
  const { modelConfig, setModelConfig } = useModelConfig()
  const { activeModal, openModal, closeModal } = useModalState()
  
  return (
    <div>
      
      
      {!isMobile && (
        
      )}
      
    </div>
  )
}

Strategy 2: Conditional Block Extraction

Extract large conditional rendering blocks.

// ❌ Before: Large conditional blocks
const AppInfo = () => {
  return (
    <div>
      {expand ? (
        <div className="expanded">
          
        </div>
      ) : (
        <div className="collapsed">
          
        </div>
      )}
    </div>
  )
}

// ✅ After: Separate view components
const AppInfoExpanded: FC = ({ appDetail, onAction }) => {
  return (
    <div className="expanded">
      
    </div>
  )
}

const AppInfoCollapsed: FC = ({ appDetail, onAction }) => {
  return (
    <div className="collapsed">
      
    </div>
  )
}

const AppInfo = () => {
  return (
    <div>
      {expand
        ? 
        : 
      }
    </div>
  )
}

Strategy 3: Modal Extraction

Extract modals with their trigger logic.

// ❌ Before: Multiple modals in one component
const AppInfo = () => {
  const [showEdit, setShowEdit] = useState(false)
  const [showDuplicate, setShowDuplicate] = useState(false)
  const [showDelete, setShowDelete] = useState(false)
  const [showSwitch, setShowSwitch] = useState(false)
  
  const onEdit = async (data) => { /* 20 lines */ }
  const onDuplicate = async (data) => { /* 20 lines */ }
  const onDelete = async () => { /* 15 lines */ }
  
  return (
    <div>
      
      
      {showEdit &&  setShowEdit(false)} />}
      {showDuplicate &&  setShowDuplicate(false)} />}
      {showDelete &&  setShowDelete(false)} />}
      {showSwitch && }
    </div>
  )
}

// ✅ After: Modal manager component
// app-info-modals.tsx
type ModalType = 'edit' | 'duplicate' | 'delete' | 'switch' | null

interface AppInfoModalsProps {
  appDetail: AppDetail
  activeModal: ModalType
  onClose: () => void
  onSuccess: () => void
}

const AppInfoModals: FC = ({
  appDetail,
  activeModal,
  onClose,
  onSuccess,
}) => {
  const handleEdit = async (data) => { /* logic */ }
  const handleDuplicate = async (data) => { /* logic */ }
  const handleDelete = async () => { /* logic */ }

  return (
    <>
      {activeModal === 'edit' && (
        
      )}
      {activeModal === 'duplicate' && (
        
      )}
      {activeModal === 'delete' && (
        
      )}
      {activeModal === 'switch' && (
        
      )}
    </>
  )
}

// Parent component
const AppInfo = () => {
  const { activeModal, openModal, closeModal } = useModalState()
  
  return (
    <div>
      
       openModal('edit')}>Edit
      
      
    </div>
  )
}

Strategy 4: List Item Extraction

Extract repeated item rendering.

// ❌ Before: Inline item rendering
const OperationsList = () => {
  return (
    <div>
      {operations.map(op => (
        <div key={op.id} className="operation-item">
          <span className="icon">{op.icon}</span>
          <span className="title">{op.title}</span>
          <span className="description">{op.description}</span>
          <button onClick={() => op.onClick()}>
            {op.actionLabel}
          </button>
          {op.badge && {op.badge}}
          
        </div>
      ))}
    </div>
  )
}

// ✅ After: Extracted item component
interface OperationItemProps {
  operation: Operation
  onAction: (id: string) => void
}

const OperationItem: FC = ({ operation, onAction }) => {
  return (
    <div className="operation-item">
      <span className="icon">{operation.icon}</span>
      <span className="title">{operation.title}</span>
      <span className="description">{operation.description}</span>
      <button onClick={() => onAction(operation.id)}>
        {operation.actionLabel}
      </button>
      {operation.badge && {operation.badge}}
    </div>
  )
}

const OperationsList = () => {
  const handleAction = useCallback((id: string) => {
    const op = operations.find(o => o.id === id)
    op?.onClick()
  }, [operations])

  return (
    <div>
      {operations.map(op => (
        
      ))}
    </div>
  )
}

Directory Structure Patterns

Pattern A: Flat Structure (Simple Components)

For components with 2-3 sub-components:

component-name/
  ├── index.tsx           # Main component
  ├── sub-component-a.tsx
  ├── sub-component-b.tsx
  └── types.ts            # Shared types

Pattern B: Nested Structure (Complex Components)

For components with many sub-components:

component-name/
  ├── index.tsx           # Main orchestration
  ├── types.ts            # Shared types
  ├── hooks/
  │   ├── use-feature-a.ts
  │   └── use-feature-b.ts
  ├── components/
  │   ├── header/
  │   │   └── index.tsx
  │   ├── content/
  │   │   └── index.tsx
  │   └── modals/
  │       └── index.tsx
  └── utils/
      └── helpers.ts

Pattern C: Feature-Based Structure (Dify Standard)

Following Dify's existing patterns:

configuration/
  ├── index.tsx           # Main page component
  ├── base/               # Base/shared components
  │   ├── feature-panel/
  │   ├── group-name/
  │   └── operation-btn/
  ├── config/             # Config section
  │   ├── index.tsx
  │   ├── agent/
  │   └── automatic/
  ├── dataset-config/     # Dataset section
  │   ├── index.tsx
  │   ├── card-item/
  │   └── params-config/
  ├── debug/              # Debug section
  │   ├── index.tsx
  │   └── hooks.tsx
  └── hooks/              # Shared hooks
      └── use-advanced-prompt-config.ts

Props Design

Minimal Props Principle

Pass only what's needed:

// ❌ Bad: Passing entire objects when only some fields needed

// ✅ Good: Destructure to minimum required

Callback Props Pattern

Use callbacks for child-to-parent communication:

// Parent
const Parent = () => {
  const [value, setValue] = useState('')
  
  return (
    
  )
}

// Child
interface ChildProps {
  value: string
  onChange: (value: string) => void
  onSubmit: () => void
}

const Child: FC = ({ value, onChange, onSubmit }) => {
  return (
    <div>
      <input value={value} onChange={e => onChange(e.target.value)} />
      <button onClick={onSubmit}>Submit</button>
    </div>
  )
}

Render Props for Flexibility

When sub-components need parent context:

interface ListProps {
  items: T[]
  renderItem: (item: T, index: number) => React.ReactNode
  renderEmpty?: () => React.ReactNode
}

function List({ items, renderItem, renderEmpty }: ListProps) {
  if (items.length === 0 && renderEmpty) {
    return <>{renderEmpty()}</>
  }
  
  return (
    <div>
      {items.map((item, index) => renderItem(item, index))}
    </div>
  )
}

// Usage
 }
  renderEmpty={() => }
/>