# Component Trees in features.json **Define entire UI hierarchies in JSON - build complete interfaces declaratively!** The `componentTrees` section in features.json allows you to define complete component hierarchies in a declarative JSON format. This enables you to build entire pages and complex UIs without writing JSX code. ## Overview Component trees support: - ✅ Nested component hierarchies - ✅ Props passing with interpolation - ✅ Conditional rendering - ✅ Loops/iterations with `forEach` - ✅ Data binding with `dataSource` - ✅ Event handlers - ✅ Dynamic values with template syntax `{{variable}}` ## Basic Structure ```json { "componentTrees": { "MyPage": { "component": "Box", "props": { "sx": { "p": 3 } }, "children": [ { "component": "Typography", "props": { "variant": "h4", "text": "Hello World" } } ] } } } ``` ## Component Node Schema ```typescript { "component": string, // Component name (e.g., "Box", "Button", "DataGrid") "props"?: object, // Component props "children"?: ComponentNode[], // Child components "condition"?: string, // Render condition (e.g., "hasPermission('create')") "forEach"?: string, // Loop over data (e.g., "items", "users") "dataSource"?: string, // Bind to data source (e.g., "tableData", "navItems") "comment"?: string // Documentation comment } ``` ## Template Syntax Use `{{variable}}` for dynamic values: ```json { "component": "Typography", "props": { "text": "Welcome, {{user.name}}!" } } ``` ### Accessing Nested Properties ```json { "component": "Typography", "props": { "text": "{{user.profile.firstName}} {{user.profile.lastName}}" } } ``` ### Using Expressions ```json { "component": "Icon", "props": { "name": "{{card.change > 0 ? 'TrendingUp' : 'TrendingDown'}}" } } ``` ## Conditional Rendering Use the `condition` property to conditionally render components: ```json { "component": "Button", "condition": "hasPermission('create')", "props": { "text": "Create New", "onClick": "openCreateDialog" } } ``` ### Multiple Conditions ```json { "condition": "features.enableSearch && userRole === 'admin'", "component": "TextField", "props": { "placeholder": "Search..." } } ``` ## Loops with forEach Iterate over arrays using `forEach`: ```json { "component": "Grid", "forEach": "users", "props": { "item": true, "xs": 12, "sm": 6 }, "children": [ { "component": "Card", "children": [ { "component": "Typography", "props": { "text": "{{user.name}}" } } ] } ] } ``` In the loop, the current item is available as the singular form of the array name: - `forEach: "users"` → current item is `{{user}}` - `forEach: "products"` → current item is `{{product}}` - `forEach: "items"` → current item is `{{item}}` ## Data Sources Bind components to data sources: ```json { "component": "NavList", "dataSource": "navItems", "children": [ { "component": "NavItem", "props": { "icon": "{{item.icon}}", "label": "{{item.label}}", "href": "/admin/{{item.id}}" } } ] } ``` ## Event Handlers Reference event handler functions by name: ```json { "component": "Button", "props": { "text": "Save", "onClick": "handleSave" } } ``` Multiple handlers: ```json { "component": "TextField", "props": { "value": "{{searchTerm}}", "onChange": "handleSearch", "onKeyPress": "handleKeyPress" } } ``` ## Complete Examples ### Admin Dashboard Layout ```json { "AdminDashboard": { "component": "Box", "props": { "sx": { "display": "flex", "minHeight": "100vh" } }, "children": [ { "component": "Sidebar", "props": { "width": 240 }, "children": [ { "component": "NavList", "dataSource": "navItems", "children": [ { "component": "NavItem", "props": { "icon": "{{item.icon}}", "label": "{{item.label}}", "href": "/admin/{{item.id}}" } } ] } ] }, { "component": "Box", "props": { "sx": { "flexGrow": 1 } }, "children": [ { "component": "AppBar", "children": [ { "component": "Toolbar", "children": [ { "component": "Typography", "props": { "variant": "h6", "text": "{{pageTitle}}" } } ] } ] }, { "component": "Outlet", "comment": "Child routes render here" } ] } ] } } ``` ### Resource List Page with CRUD Actions ```json { "ResourceListPage": { "component": "Box", "children": [ { "component": "Box", "props": { "sx": { "display": "flex", "justifyContent": "space-between", "mb": 3 } }, "children": [ { "component": "Typography", "props": { "variant": "h4", "text": "{{resourceName}}" } }, { "component": "Button", "condition": "hasPermission('create')", "props": { "variant": "contained", "startIcon": "Add", "text": "Create New", "onClick": "openCreateDialog" } } ] }, { "component": "DataGrid", "dataSource": "tableData", "props": { "columns": "{{columns}}", "rows": "{{rows}}", "onEdit": "handleEdit", "onDelete": "handleDelete" } }, { "component": "Pagination", "condition": "features.enablePagination", "props": { "count": "{{totalPages}}", "page": "{{currentPage}}", "onChange": "handlePageChange" } } ] } } ``` ### Form Dialog ```json { "FormDialogTree": { "component": "Dialog", "props": { "open": "{{open}}", "onClose": "handleClose", "maxWidth": "md" }, "children": [ { "component": "DialogTitle", "children": [ { "component": "Typography", "props": { "text": "{{title}}" } } ] }, { "component": "DialogContent", "children": [ { "component": "Grid", "props": { "container": true, "spacing": 2 }, "children": [ { "component": "Grid", "forEach": "formFields", "props": { "item": true, "xs": 12, "sm": 6 }, "children": [ { "component": "DynamicField", "props": { "field": "{{field}}", "value": "{{values[field.name]}}", "onChange": "handleFieldChange" } } ] } ] } ] }, { "component": "DialogActions", "children": [ { "component": "Button", "props": { "text": "Cancel", "onClick": "handleClose" } }, { "component": "Button", "props": { "variant": "contained", "text": "Save", "onClick": "handleSubmit", "disabled": "{{!isValid}}" } } ] } ] } } ``` ### Dashboard Stats Cards ```json { "DashboardStatsCards": { "component": "Grid", "props": { "container": true, "spacing": 3 }, "children": [ { "component": "Grid", "forEach": "statsCards", "props": { "item": true, "xs": 12, "sm": 6, "md": 3 }, "children": [ { "component": "Card", "children": [ { "component": "CardContent", "children": [ { "component": "Icon", "props": { "name": "{{card.icon}}", "color": "{{card.color}}" } }, { "component": "Typography", "props": { "variant": "h4", "text": "{{card.value}}" } }, { "component": "Typography", "props": { "variant": "body2", "text": "{{card.label}}" } } ] } ] } ] } ] } } ``` ## Using Component Trees in Code ### Get a Component Tree ```typescript import { getComponentTree } from '@/utils/featureConfig'; const tree = getComponentTree('AdminDashboard'); ``` ### Render a Component Tree ```typescript import { getComponentTree } from '@/utils/featureConfig'; function ComponentTreeRenderer({ treeName, data, handlers }: Props) { const tree = getComponentTree(treeName); if (!tree) return null; return renderNode(tree, data, handlers); } function renderNode(node: ComponentNode, data: any, handlers: any): JSX.Element { const Component = getComponent(node.component); // Evaluate condition if (node.condition && !evaluateCondition(node.condition, data)) { return null; } // Handle forEach loops if (node.forEach) { const items = data[node.forEach] || []; return ( <> {items.map((item: any, index: number) => { const itemData = { ...data, [getSingular(node.forEach)]: item }; return renderNode({ ...node, forEach: undefined }, itemData, handlers); })} ); } // Interpolate props const props = interpolateProps(node.props, data, handlers); // Render children const children = node.children?.map((child, idx) => renderNode(child, data, handlers) ); return {children}; } ``` ### Complete Example with React ```typescript import React from 'react'; import { getComponentTree } from '@/utils/featureConfig'; import { Box, Button, Typography, Dialog, TextField } from '@mui/material'; const componentMap = { Box, Button, Typography, Dialog, TextField, // ... other components }; function DynamicPage({ treeName }: { treeName: string }) { const tree = getComponentTree(treeName); const [data, setData] = useState({ pageTitle: 'Users Management', resourceName: 'Users', rows: [], loading: false, }); const handlers = { handleEdit: (row: any) => console.log('Edit', row), handleDelete: (row: any) => console.log('Delete', row), openCreateDialog: () => console.log('Create'), }; return renderComponentTree(tree, data, handlers); } ``` ## Benefits of Component Trees 1. **Declarative UI**: Define UIs in configuration, not code 2. **Rapid Prototyping**: Build pages quickly without JSX 3. **Non-Technical Edits**: Allow non-developers to modify UI structure 4. **Consistency**: Enforce consistent component usage 5. **Dynamic Generation**: Generate UIs from API responses 6. **A/B Testing**: Easily swap component trees 7. **Version Control**: Track UI changes in JSON 8. **Hot Reloading**: Update UIs without code changes 9. **Multi-Platform**: Same tree can target web, mobile, etc. 10. **Reduced Code**: Less boilerplate, more configuration ## Best Practices 1. **Keep trees shallow**: Deep nesting is hard to maintain 2. **Use meaningful names**: `UserListPage` not `Page1` 3. **Document with comments**: Add `comment` fields for clarity 4. **Group related trees**: Organize by feature or page 5. **Validate props**: Ensure required props are present 6. **Test conditions**: Verify conditional logic works 7. **Handle missing data**: Provide fallbacks for `{{variables}}` 8. **Reuse subtrees**: Extract common patterns 9. **Type checking**: Use TypeScript for component props 10. **Version trees**: Track changes in version control ## Advanced Features ### Computed Values ```json { "component": "Typography", "props": { "text": "{{items.length}} items found" } } ``` ### Nested Conditionals ```json { "condition": "user.role === 'admin'", "component": "Box", "children": [ { "condition": "user.permissions.includes('delete')", "component": "Button", "props": { "text": "Delete All", "onClick": "handleDeleteAll" } } ] } ``` ### Dynamic Component Selection ```json { "component": "{{viewType === 'grid' ? 'GridView' : 'ListView'}}", "props": { "items": "{{items}}" } } ``` ## API Reference ### `getComponentTree(treeName: string): ComponentTree | undefined` Get a component tree by name. ```typescript const tree = getComponentTree('AdminDashboard'); ``` ### `getAllComponentTrees(): Record` Get all defined component trees. ```typescript const trees = getAllComponentTrees(); console.log(Object.keys(trees)); // ['AdminDashboard', 'ResourceListPage', ...] ``` ## Conclusion Component trees in features.json enable you to: - Build complete UIs without writing JSX - Define page layouts declaratively - Create dynamic, data-driven interfaces - Rapidly prototype and iterate - **Build half your app from configuration!** With component trees, features.json becomes a complete UI definition language, enabling true configuration-driven development.