# 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
1. **Conditional rendering blocks** - Large `{condition && }` blocks
1. **Repeated patterns** - Similar UI structures used multiple times
1. **300+ lines** - Component exceeds manageable size
1. **Modal clusters** - Multiple modals rendered in one component
## Splitting Strategies
### Strategy 1: Section-Based Splitting
Identify visual sections and extract each as a component.
```typescript
// ❌ Before: Monolithic component (500+ lines)
const ConfigurationPage = () => {
return (
{/* Header Section - 50 lines */}
{t('configuration.title')}
{isAdvancedMode &&
Advanced }
{/* Config Section - 200 lines */}
{/* Debug Section - 150 lines */}
{/* Modals Section - 100 lines */}
{showSelectDataSet &&
}
{showHistoryModal &&
}
{showUseGPT4Confirm &&
}
)
}
// ✅ 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 (
{t('configuration.title')}
{isAdvancedMode &&
Advanced }
)
}
// index.tsx (orchestration only)
const ConfigurationPage = () => {
const { modelConfig, setModelConfig } = useModelConfig()
const { activeModal, openModal, closeModal } = useModalState()
return (
{!isMobile && (
)}
)
}
```
### Strategy 2: Conditional Block Extraction
Extract large conditional rendering blocks.
```typescript
// ❌ Before: Large conditional blocks
const AppInfo = () => {
return (
{expand ? (
{/* 100 lines of expanded view */}
) : (
{/* 50 lines of collapsed view */}
)}
)
}
// ✅ After: Separate view components
const AppInfoExpanded: FC = ({ appDetail, onAction }) => {
return (
{/* Clean, focused expanded view */}
)
}
const AppInfoCollapsed: FC = ({ appDetail, onAction }) => {
return (
{/* Clean, focused collapsed view */}
)
}
const AppInfo = () => {
return (
)
}
```
### Strategy 3: Modal Extraction
Extract modals with their trigger logic.
```typescript
// ❌ 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 (
{/* Main content */}
{showEdit && setShowEdit(false)} />}
{showDuplicate && setShowDuplicate(false)} />}
{showDelete && setShowDelete(false)} />}
{showSwitch && }
)
}
// ✅ 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 (
{/* Main content with openModal triggers */}
openModal('edit')}>Edit
)
}
```
### Strategy 4: List Item Extraction
Extract repeated item rendering.
```typescript
// ❌ Before: Inline item rendering
const OperationsList = () => {
return (
{operations.map(op => (
{op.icon}
{op.title}
{op.description}
op.onClick()}>
{op.actionLabel}
{op.badge && {op.badge} }
{/* More complex rendering... */}
))}
)
}
// ✅ After: Extracted item component
interface OperationItemProps {
operation: Operation
onAction: (id: string) => void
}
const OperationItem: FC = ({ operation, onAction }) => {
return (
{operation.icon}
{operation.title}
{operation.description}
onAction(operation.id)}>
{operation.actionLabel}
{operation.badge && {operation.badge} }
)
}
const OperationsList = () => {
const handleAction = useCallback((id: string) => {
const op = operations.find(o => o.id === id)
op?.onClick()
}, [operations])
return (
{operations.map(op => (
))}
)
}
```
## 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:
```typescript
// ❌ Bad: Passing entire objects when only some fields needed
// ✅ Good: Destructure to minimum required
```
### Callback Props Pattern
Use callbacks for child-to-parent communication:
```typescript
// 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 (
onChange(e.target.value)} />
Submit
)
}
```
### Render Props for Flexibility
When sub-components need parent context:
```typescript
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 (
{items.map((item, index) => renderItem(item, index))}
)
}
// Usage
}
renderEmpty={() => }
/>
```