fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
717
docs/livecomponents/livecomponent-nested-components.md
Normal file
717
docs/livecomponents/livecomponent-nested-components.md
Normal file
@@ -0,0 +1,717 @@
|
||||
# LiveComponents Nested Components System
|
||||
|
||||
Comprehensive guide for building nested component hierarchies with parent-child relationships, event bubbling, and state synchronization.
|
||||
|
||||
## Overview
|
||||
|
||||
The Nested Components System enables complex UI compositions through parent-child component relationships. Parents can manage global state while children handle localized behavior, with events bubbling up for coordination.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Parent Component │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Global State Management │ │
|
||||
│ │ • Manages list of items │ │
|
||||
│ │ • Provides data to children │ │
|
||||
│ │ • Handles child events │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────┴────────────┐ │
|
||||
│ │ │ │
|
||||
│ ┌─────────▼────────┐ ┌──────────▼────────┐ │
|
||||
│ │ Child Component │ │ Child Component │ │
|
||||
│ │ • Local State │ │ • Local State │ │
|
||||
│ │ • Dispatch Events│ │ • Dispatch Events│ │
|
||||
│ └──────────────────┘ └───────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### 1. Component Hierarchy
|
||||
|
||||
**ComponentHierarchy Value Object** - Represents parent-child relationships:
|
||||
|
||||
```php
|
||||
use App\Framework\LiveComponents\ValueObjects\ComponentHierarchy;
|
||||
use App\Framework\LiveComponents\ValueObjects\ComponentId;
|
||||
|
||||
// Root component (no parent)
|
||||
$rootHierarchy = ComponentHierarchy::root();
|
||||
// depth=0, path=[]
|
||||
|
||||
// First-level child
|
||||
$childHierarchy = ComponentHierarchy::fromParent(
|
||||
parentId: ComponentId::fromString('parent:main'),
|
||||
childId: ComponentId::fromString('child:1')
|
||||
);
|
||||
// depth=1, path=['parent:main', 'child:1']
|
||||
|
||||
// Add another level
|
||||
$grandchildHierarchy = $childHierarchy->withChild(
|
||||
ComponentId::fromString('grandchild:1')
|
||||
);
|
||||
// depth=2, path=['parent:main', 'child:1', 'grandchild:1']
|
||||
```
|
||||
|
||||
**Hierarchy Queries:**
|
||||
```php
|
||||
$hierarchy->isRoot(); // true if no parent
|
||||
$hierarchy->isChild(); // true if has parent
|
||||
$hierarchy->getLevel(); // nesting depth (0, 1, 2, ...)
|
||||
$hierarchy->isDescendantOf($componentId); // check ancestry
|
||||
```
|
||||
|
||||
### 2. NestedComponentManager
|
||||
|
||||
**Server-Side Hierarchy Management:**
|
||||
|
||||
```php
|
||||
use App\Framework\LiveComponents\NestedComponentManager;
|
||||
|
||||
$manager = new NestedComponentManager();
|
||||
|
||||
// Register root component
|
||||
$parentId = ComponentId::fromString('todo-list:main');
|
||||
$manager->registerHierarchy($parentId, ComponentHierarchy::root());
|
||||
|
||||
// Register child
|
||||
$childId = ComponentId::fromString('todo-item:1');
|
||||
$childHierarchy = ComponentHierarchy::fromParent($parentId, $childId);
|
||||
$manager->registerHierarchy($childId, $childHierarchy);
|
||||
|
||||
// Query hierarchy
|
||||
$manager->hasChildren($parentId); // true
|
||||
$manager->getChildIds($parentId); // [ComponentId('todo-item:1')]
|
||||
$manager->getParentId($childId); // ComponentId('todo-list:main')
|
||||
$manager->isRoot($parentId); // true
|
||||
$manager->getDepth($childId); // 1
|
||||
|
||||
// Get all ancestors/descendants
|
||||
$ancestors = $manager->getAncestors($childId); // [parentId]
|
||||
$descendants = $manager->getDescendants($parentId); // [childId]
|
||||
|
||||
// Statistics
|
||||
$stats = $manager->getStats();
|
||||
// ['total_components' => 2, 'root_components' => 1, ...]
|
||||
```
|
||||
|
||||
### 3. SupportsNesting Interface
|
||||
|
||||
**Parent components must implement this interface:**
|
||||
|
||||
```php
|
||||
use App\Framework\LiveComponents\Contracts\SupportsNesting;
|
||||
|
||||
interface SupportsNesting
|
||||
{
|
||||
/**
|
||||
* Get list of child component IDs
|
||||
*/
|
||||
public function getChildComponents(): array;
|
||||
|
||||
/**
|
||||
* Handle event from child component
|
||||
*
|
||||
* @return bool Return false to stop event bubbling, true to continue
|
||||
*/
|
||||
public function onChildEvent(ComponentId $childId, string $eventName, array $payload): bool;
|
||||
|
||||
/**
|
||||
* Validate child component compatibility
|
||||
*/
|
||||
public function canHaveChild(ComponentId $childId): bool;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Event Bubbling
|
||||
|
||||
**Events flow from child to parent:**
|
||||
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ Child Component │
|
||||
│ • User clicks button │
|
||||
│ • Dispatches event │
|
||||
└───────────┬─────────────┘
|
||||
│ Event Bubbles Up
|
||||
▼
|
||||
┌─────────────────────────┐
|
||||
│ Parent Component │
|
||||
│ • Receives event │
|
||||
│ • Updates state │
|
||||
│ • Re-renders children │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
**Event Dispatcher:**
|
||||
```php
|
||||
use App\Framework\LiveComponents\NestedComponentEventDispatcher;
|
||||
|
||||
$dispatcher = new NestedComponentEventDispatcher();
|
||||
|
||||
// Child dispatches event
|
||||
$dispatcher->dispatch(
|
||||
componentId: ComponentId::fromString('todo-item:1'),
|
||||
eventName: 'todo-completed',
|
||||
payload: [
|
||||
'todo_id' => '1',
|
||||
'completed' => true
|
||||
]
|
||||
);
|
||||
|
||||
// Check dispatched events
|
||||
$dispatcher->hasEvents(); // true
|
||||
$dispatcher->count(); // 1
|
||||
$events = $dispatcher->getEvents();
|
||||
```
|
||||
|
||||
## Implementation Guide
|
||||
|
||||
### Creating a Parent Component
|
||||
|
||||
**1. Implement SupportsNesting:**
|
||||
|
||||
```php
|
||||
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
|
||||
use App\Framework\LiveComponents\Contracts\SupportsNesting;
|
||||
use App\Framework\LiveComponents\Attributes\LiveComponent;
|
||||
|
||||
#[LiveComponent('todo-list')]
|
||||
final readonly class TodoListComponent implements LiveComponentContract, SupportsNesting
|
||||
{
|
||||
private ComponentId $id;
|
||||
private TodoListState $state;
|
||||
|
||||
public function __construct(
|
||||
ComponentId $id,
|
||||
?ComponentData $initialData = null,
|
||||
array $todos = []
|
||||
) {
|
||||
$this->id = $id;
|
||||
$this->state = $initialData
|
||||
? TodoListState::fromComponentData($initialData)
|
||||
: new TodoListState(todos: $todos);
|
||||
}
|
||||
|
||||
// LiveComponentContract methods
|
||||
public function getId(): ComponentId { return $this->id; }
|
||||
public function getData(): ComponentData { return $this->state->toComponentData(); }
|
||||
public function getRenderData(): ComponentRenderData { /* ... */ }
|
||||
|
||||
// SupportsNesting methods
|
||||
|
||||
public function getChildComponents(): array
|
||||
{
|
||||
// Return array of child component IDs
|
||||
$childIds = [];
|
||||
foreach ($this->state->todos as $todo) {
|
||||
$childIds[] = "todo-item:{$todo['id']}";
|
||||
}
|
||||
return $childIds;
|
||||
}
|
||||
|
||||
public function onChildEvent(ComponentId $childId, string $eventName, array $payload): bool
|
||||
{
|
||||
// Handle events from children
|
||||
match ($eventName) {
|
||||
'todo-completed' => $this->handleTodoCompleted($payload),
|
||||
'todo-deleted' => $this->handleTodoDeleted($payload),
|
||||
default => null
|
||||
};
|
||||
|
||||
return true; // Continue bubbling
|
||||
}
|
||||
|
||||
public function canHaveChild(ComponentId $childId): bool
|
||||
{
|
||||
// Only accept TodoItem components
|
||||
return str_starts_with($childId->name, 'todo-item');
|
||||
}
|
||||
|
||||
private function handleTodoCompleted(array $payload): void
|
||||
{
|
||||
$todoId = $payload['todo_id'];
|
||||
$completed = $payload['completed'];
|
||||
|
||||
// Log or trigger side effects
|
||||
error_log("Todo {$todoId} marked as " . ($completed ? 'completed' : 'active'));
|
||||
|
||||
// Note: State updates happen through Actions, not event handlers
|
||||
// Event handlers are for logging, analytics, side effects
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**2. Create Parent State:**
|
||||
|
||||
```php
|
||||
final readonly class TodoListState
|
||||
{
|
||||
public function __construct(
|
||||
public array $todos = [],
|
||||
public string $filter = 'all'
|
||||
) {}
|
||||
|
||||
public static function fromComponentData(ComponentData $data): self
|
||||
{
|
||||
$array = $data->toArray();
|
||||
return new self(
|
||||
todos: $array['todos'] ?? [],
|
||||
filter: $array['filter'] ?? 'all'
|
||||
);
|
||||
}
|
||||
|
||||
public function toComponentData(): ComponentData
|
||||
{
|
||||
return ComponentData::fromArray([
|
||||
'todos' => $this->todos,
|
||||
'filter' => $this->filter
|
||||
]);
|
||||
}
|
||||
|
||||
public function withTodoAdded(array $todo): self
|
||||
{
|
||||
return new self(
|
||||
todos: [...$this->todos, $todo],
|
||||
filter: $this->filter
|
||||
);
|
||||
}
|
||||
|
||||
// More transformation methods...
|
||||
}
|
||||
```
|
||||
|
||||
**3. Create Parent Template:**
|
||||
|
||||
```html
|
||||
<!-- todo-list.view.php -->
|
||||
<div class="todo-list">
|
||||
<!-- Parent UI -->
|
||||
<h2>My Todos ({total_count})</h2>
|
||||
|
||||
<!-- Child Components -->
|
||||
<for items="todos" as="todo">
|
||||
<div
|
||||
data-live-component="todo-item:{todo.id}"
|
||||
data-parent-component="{component_id}"
|
||||
data-nesting-depth="1"
|
||||
>
|
||||
<!-- TodoItemComponent renders here -->
|
||||
</div>
|
||||
</for>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Creating a Child Component
|
||||
|
||||
**1. Implement Component with Event Dispatcher:**
|
||||
|
||||
```php
|
||||
use App\Framework\LiveComponents\NestedComponentEventDispatcher;
|
||||
|
||||
#[LiveComponent('todo-item')]
|
||||
final readonly class TodoItemComponent implements LiveComponentContract
|
||||
{
|
||||
private ComponentId $id;
|
||||
private TodoItemState $state;
|
||||
|
||||
public function __construct(
|
||||
ComponentId $id,
|
||||
private NestedComponentEventDispatcher $eventDispatcher,
|
||||
?ComponentData $initialData = null,
|
||||
?array $todoData = null
|
||||
) {
|
||||
$this->id = $id;
|
||||
$this->state = $initialData
|
||||
? TodoItemState::fromComponentData($initialData)
|
||||
: TodoItemState::fromTodoArray($todoData ?? []);
|
||||
}
|
||||
|
||||
#[Action]
|
||||
public function toggle(): ComponentData
|
||||
{
|
||||
$newState = $this->state->withToggled();
|
||||
|
||||
// Dispatch event to parent
|
||||
$this->eventDispatcher->dispatch(
|
||||
componentId: $this->id,
|
||||
eventName: 'todo-completed',
|
||||
payload: [
|
||||
'todo_id' => $this->state->id,
|
||||
'completed' => $newState->completed
|
||||
]
|
||||
);
|
||||
|
||||
return $newState->toComponentData();
|
||||
}
|
||||
|
||||
#[Action]
|
||||
public function delete(): ComponentData
|
||||
{
|
||||
// Dispatch delete event to parent
|
||||
$this->eventDispatcher->dispatch(
|
||||
componentId: $this->id,
|
||||
eventName: 'todo-deleted',
|
||||
payload: ['todo_id' => $this->state->id]
|
||||
);
|
||||
|
||||
return $this->state->toComponentData();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**2. Create Child State:**
|
||||
|
||||
```php
|
||||
final readonly class TodoItemState
|
||||
{
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public string $title,
|
||||
public bool $completed = false,
|
||||
public int $createdAt = 0
|
||||
) {}
|
||||
|
||||
public static function fromTodoArray(array $todo): self
|
||||
{
|
||||
return new self(
|
||||
id: $todo['id'] ?? '',
|
||||
title: $todo['title'] ?? '',
|
||||
completed: $todo['completed'] ?? false,
|
||||
createdAt: $todo['created_at'] ?? time()
|
||||
);
|
||||
}
|
||||
|
||||
public function withToggled(): self
|
||||
{
|
||||
return new self(
|
||||
id: $this->id,
|
||||
title: $this->title,
|
||||
completed: !$this->completed,
|
||||
createdAt: $this->createdAt
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3. Create Child Template:**
|
||||
|
||||
```html
|
||||
<!-- todo-item.view.php -->
|
||||
<div class="todo-item {completed|then:todo-item--completed}">
|
||||
<button data-livecomponent-action="toggle">
|
||||
<if condition="completed">✓</if>
|
||||
</button>
|
||||
|
||||
<div class="todo-item__title">{title}</div>
|
||||
|
||||
<button data-livecomponent-action="delete">✕</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Client-Side Integration
|
||||
|
||||
**Automatic Initialization:**
|
||||
|
||||
```javascript
|
||||
// NestedComponentHandler automatically initializes with LiveComponentManager
|
||||
import { LiveComponentManager } from './livecomponent/index.js';
|
||||
|
||||
// Scans DOM for nested components
|
||||
const nestedHandler = liveComponentManager.nestedHandler;
|
||||
|
||||
// Get hierarchy info
|
||||
const parentId = nestedHandler.getParentId('todo-item:1'); // 'todo-list:main'
|
||||
const childIds = nestedHandler.getChildIds('todo-list:main'); // ['todo-item:1', ...]
|
||||
|
||||
// Event bubbling
|
||||
nestedHandler.bubbleEvent('todo-item:1', 'todo-completed', {
|
||||
todo_id: '1',
|
||||
completed: true
|
||||
});
|
||||
|
||||
// Statistics
|
||||
const stats = nestedHandler.getStats();
|
||||
// { total_components: 5, root_components: 1, max_nesting_depth: 2, ... }
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. State Management
|
||||
|
||||
**✅ Parent owns the data:**
|
||||
```php
|
||||
// Parent manages list
|
||||
private TodoListState $state; // Contains all todos
|
||||
|
||||
// Child manages display state only
|
||||
private TodoItemState $state; // id, title, completed, isEditing
|
||||
```
|
||||
|
||||
**❌ Don't duplicate state:**
|
||||
```php
|
||||
// Bad: Both parent and child store todo data
|
||||
// This leads to synchronization issues
|
||||
```
|
||||
|
||||
### 2. Event Handling
|
||||
|
||||
**✅ Use event handlers for side effects:**
|
||||
```php
|
||||
public function onChildEvent(ComponentId $childId, string $eventName, array $payload): bool
|
||||
{
|
||||
// ✅ Logging
|
||||
error_log("Child event: {$eventName}");
|
||||
|
||||
// ✅ Analytics
|
||||
$this->analytics->track($eventName, $payload);
|
||||
|
||||
// ✅ External system updates
|
||||
$this->cache->invalidate($payload['todo_id']);
|
||||
|
||||
return true; // Continue bubbling
|
||||
}
|
||||
```
|
||||
|
||||
**❌ Don't modify state in event handlers:**
|
||||
```php
|
||||
// ❌ Bad: Event handlers shouldn't modify component state
|
||||
// State changes happen through Actions that return new ComponentData
|
||||
```
|
||||
|
||||
### 3. Child Compatibility
|
||||
|
||||
**✅ Validate child types:**
|
||||
```php
|
||||
public function canHaveChild(ComponentId $childId): bool
|
||||
{
|
||||
// Only accept specific component types
|
||||
return str_starts_with($childId->name, 'todo-item');
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Circular Dependencies
|
||||
|
||||
**✅ Framework automatically prevents:**
|
||||
```php
|
||||
// This will throw InvalidArgumentException:
|
||||
$manager->registerHierarchy($componentId, $hierarchy);
|
||||
// "Circular dependency detected: Component cannot be its own ancestor"
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Hierarchy Depth
|
||||
|
||||
- **Recommended:** Max 3-4 levels deep
|
||||
- **Reason:** Each level adds overhead for event bubbling
|
||||
- **Alternative:** Flatten hierarchy when possible
|
||||
|
||||
### Event Bubbling
|
||||
|
||||
- **Cost:** O(depth) for each event
|
||||
- **Optimization:** Stop bubbling early when not needed
|
||||
- **Pattern:** Return `false` from `onChildEvent()` to stop
|
||||
|
||||
```php
|
||||
public function onChildEvent(ComponentId $childId, string $eventName, array $payload): bool
|
||||
{
|
||||
if ($eventName === 'internal-event') {
|
||||
// Handle locally, don't bubble further
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let other events bubble
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### State Synchronization
|
||||
|
||||
- **Pattern:** Parent as single source of truth
|
||||
- **Benefit:** Avoids synchronization bugs
|
||||
- **Trade-off:** More re-renders, but simpler logic
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Tests
|
||||
|
||||
```php
|
||||
describe('NestedComponentManager', function () {
|
||||
it('tracks parent-child relationships', function () {
|
||||
$manager = new NestedComponentManager();
|
||||
|
||||
$parentId = ComponentId::fromString('parent:1');
|
||||
$childId = ComponentId::fromString('child:1');
|
||||
|
||||
$manager->registerHierarchy($parentId, ComponentHierarchy::root());
|
||||
$manager->registerHierarchy(
|
||||
$childId,
|
||||
ComponentHierarchy::fromParent($parentId, $childId)
|
||||
);
|
||||
|
||||
expect($manager->hasChildren($parentId))->toBeTrue();
|
||||
expect($manager->getParentId($childId))->toEqual($parentId);
|
||||
});
|
||||
|
||||
it('prevents circular dependencies', function () {
|
||||
$manager = new NestedComponentManager();
|
||||
$id = ComponentId::fromString('self:1');
|
||||
|
||||
expect(fn() => $manager->registerHierarchy(
|
||||
$id,
|
||||
ComponentHierarchy::fromParent($id, $id)
|
||||
))->toThrow(InvalidArgumentException::class);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
```php
|
||||
describe('TodoList with nested TodoItems', function () {
|
||||
it('handles child events', function () {
|
||||
$todoList = new TodoListComponent(
|
||||
id: ComponentId::fromString('todo-list:test'),
|
||||
todos: [
|
||||
['id' => '1', 'title' => 'Test', 'completed' => false]
|
||||
]
|
||||
);
|
||||
|
||||
$childId = ComponentId::fromString('todo-item:1');
|
||||
|
||||
// Simulate child event
|
||||
$result = $todoList->onChildEvent(
|
||||
$childId,
|
||||
'todo-completed',
|
||||
['todo_id' => '1', 'completed' => true]
|
||||
);
|
||||
|
||||
expect($result)->toBeTrue(); // Event bubbled successfully
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Problem: Children not rendering
|
||||
|
||||
**Cause:** Missing `data-parent-component` attribute
|
||||
|
||||
**Solution:**
|
||||
```html
|
||||
<!-- ✅ Correct -->
|
||||
<div
|
||||
data-live-component="child:1"
|
||||
data-parent-component="parent:main"
|
||||
data-nesting-depth="1"
|
||||
>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Problem: Events not bubbling
|
||||
|
||||
**Cause:** Wrong ComponentId or event name
|
||||
|
||||
**Solution:**
|
||||
```php
|
||||
// ✅ Use exact component ID
|
||||
$this->eventDispatcher->dispatch(
|
||||
componentId: $this->id, // ✅ Correct: use component's own ID
|
||||
eventName: 'todo-completed',
|
||||
payload: [...]
|
||||
);
|
||||
```
|
||||
|
||||
### Problem: Circular dependency error
|
||||
|
||||
**Cause:** Component trying to be its own ancestor
|
||||
|
||||
**Solution:**
|
||||
```php
|
||||
// ❌ Wrong: Same component as parent and child
|
||||
$hierarchy = ComponentHierarchy::fromParent($sameId, $sameId);
|
||||
|
||||
// ✅ Correct: Different components
|
||||
$hierarchy = ComponentHierarchy::fromParent($parentId, $childId);
|
||||
```
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Multi-Level Nesting
|
||||
|
||||
```php
|
||||
// Grandparent → Parent → Child
|
||||
$grandparent = ComponentHierarchy::root();
|
||||
|
||||
$parent = ComponentHierarchy::fromParent(
|
||||
ComponentId::fromString('grandparent:1'),
|
||||
ComponentId::fromString('parent:1')
|
||||
);
|
||||
|
||||
$child = $parent->withChild(
|
||||
ComponentId::fromString('child:1')
|
||||
);
|
||||
// depth=2, path=['grandparent:1', 'parent:1', 'child:1']
|
||||
```
|
||||
|
||||
### Conditional Children
|
||||
|
||||
```php
|
||||
public function getChildComponents(): array
|
||||
{
|
||||
// Only show children if filter matches
|
||||
$filteredTodos = $this->state->getFilteredTodos();
|
||||
|
||||
return array_map(
|
||||
fn($todo) => "todo-item:{$todo['id']}",
|
||||
$filteredTodos
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Dynamic Child Addition
|
||||
|
||||
```php
|
||||
#[Action]
|
||||
public function addTodo(string $title): ComponentData
|
||||
{
|
||||
$newTodo = [
|
||||
'id' => uniqid('todo_', true),
|
||||
'title' => $title,
|
||||
'completed' => false
|
||||
];
|
||||
|
||||
// State includes new todo
|
||||
$newState = $this->state->withTodoAdded($newTodo);
|
||||
|
||||
// Framework automatically creates child component
|
||||
// based on getChildComponents() result
|
||||
|
||||
return $newState->toComponentData();
|
||||
}
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
**Nested Components enable:**
|
||||
- ✅ Complex UI compositions
|
||||
- ✅ Parent-child communication via events
|
||||
- ✅ Hierarchical state management
|
||||
- ✅ Reusable component patterns
|
||||
- ✅ Type-safe relationships
|
||||
|
||||
**Key Classes:**
|
||||
- `ComponentHierarchy` - Relationship value object
|
||||
- `NestedComponentManager` - Server-side hierarchy
|
||||
- `NestedComponentHandler` - Client-side hierarchy
|
||||
- `NestedComponentEventDispatcher` - Event bubbling
|
||||
- `SupportsNesting` - Parent component interface
|
||||
|
||||
**Next Steps:**
|
||||
- Implement Slot System for flexible composition
|
||||
- Add SSE integration for real-time updates
|
||||
- Explore advanced caching strategies
|
||||
Reference in New Issue
Block a user