9.7 KiB
UI Refactoring Summary: Component Trees in features.json
Overview
This refactoring successfully moved UI boilerplate code from React components into the features.json configuration file, creating a more declarative and maintainable architecture.
What Was Changed
1. New ComponentTreeRenderer Utility
Created /src/utils/ComponentTreeRenderer.tsx - a powerful utility that renders React component trees from JSON configuration:
Features:
- ✅ Renders nested component hierarchies from JSON
- ✅ Supports template interpolation (
{{variable}}) - ✅ Conditional rendering with
conditionproperty - ✅ Loops/iterations with
forEachproperty - ✅ Event handler binding
- ✅ Icon component mapping
- ✅ Material-UI component integration
2. Expanded features.json Schema
Added new component trees to /src/config/features.json:
Component Trees Added:
- TableManagerTab - UI for creating and managing database tables
- ColumnManagerTab - UI for adding, modifying, and dropping columns
- ConstraintManagerTab - UI for managing table constraints
- IndexManagerTab - UI for creating and managing indexes
- QueryBuilderTab - Visual query builder interface
Each component tree defines the complete UI structure declaratively in JSON format.
3. Refactored Components
Before: Boilerplate JSX Code
// Old TableManagerTab.tsx - 116 lines with hardcoded JSX
return (
<>
<Typography variant="h5" gutterBottom>
{feature?.name || 'Table Manager'}
</Typography>
<Box sx={{ mt: 2, mb: 2 }}>
{canCreate && (
<Button variant="contained" startIcon={<AddIcon />} ...>
Create Table
</Button>
)}
// ... more boilerplate
</Box>
// ... more JSX
</>
);
After: Configuration-Driven
// New TableManagerTab.tsx - 67 lines (42% reduction)
const tree = getComponentTree('TableManagerTab');
const data = { feature, tables, canCreate, canDelete };
const handlers = { openCreateDialog, openDropDialog };
return (
<ComponentTreeRenderer tree={tree} data={data} handlers={handlers} />
);
Benefits of This Refactoring
1. Reduced Code Duplication
- UI structure defined once in JSON
- Components become thin wrappers with business logic only
- TableManagerTab: 116 → 67 lines (42% reduction)
- ColumnManagerTab: 215 → 133 lines (38% reduction)
2. Declarative UI Definition
- UI structure is now data, not code
- Easier to modify without touching TypeScript/React
- Non-developers can understand and modify UI structure
3. Consistent Component Usage
- All UIs use the same Material-UI components
- Enforces consistency across the application
- Easier to apply global UI changes
4. Better Separation of Concerns
- UI structure (features.json) separated from business logic (component files)
- Event handlers and state management remain in components
- Data fetching and API calls stay in components
5. Easier Testing
- Component logic can be tested independently of UI structure
- UI structure can be validated as JSON schema
- Atomic components (DataGrid, ConfirmDialog) remain fully testable
6. Configuration-Driven Development
- Features can be defined entirely in JSON
- Reduces need for React/TypeScript knowledge
- Enables rapid prototyping and iteration
Architecture
┌─────────────────────────────────────────────────────┐
│ features.json │
│ ┌──────────────────────────────────────┐ │
│ │ Component Trees │ │
│ │ - TableManagerTab │ │
│ │ - ColumnManagerTab │ │
│ │ - IndexManagerTab │ │
│ │ - ConstraintManagerTab │ │
│ │ - QueryBuilderTab │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ ComponentTreeRenderer │
│ - Parses JSON component tree │
│ - Interpolates data and expressions │
│ - Evaluates conditions │
│ - Handles loops (forEach) │
│ - Binds event handlers │
│ - Renders React components │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Refactored Components │
│ - Define state and business logic │
│ - Handle events and data fetching │
│ - Pass data and handlers to renderer │
│ - Keep atomic dialogs (CreateTableDialog, etc.) │
└─────────────────────────────────────────────────────┘
Atomic Components Retained
These components remain as-is (atomic, reusable building blocks):
- ✅ DataGrid - Table display with edit/delete actions
- ✅ ConfirmDialog - Confirmation dialog for destructive actions
- ✅ FormDialog - Generic form dialog
- ✅ CreateTableDialog - Specialized table creation dialog
- ✅ DropTableDialog - Table deletion dialog
- ✅ ColumnDialog - Column add/modify/drop dialog
- ✅ ConstraintDialog - Constraint management dialog
Component Tree Schema
type ComponentNode = {
component: string; // Component name (e.g., "Box", "Button")
props?: Record<string, any>; // Component props
children?: ComponentNode[]; // Nested children
condition?: string; // Render condition (e.g., "canCreate")
forEach?: string; // Loop over array (e.g., "tables")
dataSource?: string; // Data binding
comment?: string; // Documentation
};
Example Component Tree
{
"TableManagerTab": {
"component": "Box",
"children": [
{
"component": "Typography",
"props": {
"variant": "h5",
"gutterBottom": true,
"text": "{{feature.name}}"
}
},
{
"component": "Button",
"condition": "canCreate",
"props": {
"variant": "contained",
"startIcon": "Add",
"onClick": "openCreateDialog",
"text": "Create Table"
}
},
{
"component": "List",
"children": [
{
"component": "ListItem",
"forEach": "tables",
"children": [...]
}
]
}
]
}
}
Future Enhancements
Potential Improvements:
- More Component Trees - Add component trees for remaining large components
- Component Library - Expand component map with more Material-UI components
- Tree Validation - Add JSON schema validation for component trees
- Visual Editor - Create a visual editor for component trees
- Hot Reloading - Enable live updates when features.json changes
- A/B Testing - Switch between different component tree versions
- Multi-Platform - Use same trees for web and mobile
Components to Refactor Next:
- QueryBuilderTab (413 lines → can be reduced significantly)
- IndexManagerTab (434 lines → can be reduced significantly)
- ConstraintManagerTab (203 lines → can be reduced significantly)
Migration Guide
To refactor a component to use ComponentTreeRenderer:
Step 1: Define Component Tree in features.json
{
"componentTrees": {
"YourComponentName": {
"component": "Box",
"children": [
// Define your UI structure here
]
}
}
}
Step 2: Refactor Component
import { getComponentTree } from '@/utils/featureConfig';
import ComponentTreeRenderer from '@/utils/ComponentTreeRenderer';
export default function YourComponent(props) {
const [state, setState] = useState(/* ... */);
const tree = getComponentTree('YourComponentName');
const data = { /* your data */ };
const handlers = { /* your event handlers */ };
return (
<>
<ComponentTreeRenderer tree={tree} data={data} handlers={handlers} />
{/* Keep atomic components like dialogs here */}
</>
);
}
Step 3: Test
- Verify UI renders correctly
- Check conditional rendering
- Test event handlers
- Validate loops/iterations
Conclusion
This refactoring successfully demonstrates the power of configuration-driven UI development. By moving UI boilerplate to JSON, we've:
- ✅ Reduced code by 38-42% in refactored components
- ✅ Improved maintainability and consistency
- ✅ Enabled non-developers to modify UI structure
- ✅ Created a foundation for rapid feature development
- ✅ Maintained atomic component library for complex interactions
The architecture is scalable and can be extended to cover more components in the future.