# 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 (
{expand ? : }
) } ``` ### 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 */}
) } ``` ### 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.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} {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)} />
) } ``` ### 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={() => } /> ```