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:
- Multiple UI sections - Distinct visual areas with minimal coupling that can be composed independently
- Conditional rendering blocks - Large
{condition && }blocks - Repeated patterns - Similar UI structures used multiple times
- 300+ lines - Component exceeds manageable size
- 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={() => }
/>