# Advanced LiveComponents Features **Deep Dive into Advanced LiveComponents Functionality** This guide explores advanced features for building sophisticated, high-performance LiveComponents applications. --- ## Table of Contents 1. [Fragment-Based Rendering](#fragment-based-rendering) 2. [Request Batching](#request-batching) 3. [Server-Sent Events (SSE)](#server-sent-events-sse) 4. [Optimistic UI Updates](#optimistic-ui-updates) 5. [Chunked File Uploads](#chunked-file-uploads) 6. [Component Communication](#component-communication) 7. [State Management Patterns](#state-management-patterns) 8. [Custom Actions](#custom-actions) --- ## Fragment-Based Rendering ### Overview Fragment rendering enables updating specific parts of a component without re-rendering the entire component DOM tree. **Benefits**: - 70-90% reduction in DOM updates - Preserved focus and scroll position - Improved perceived performance - Lower bandwidth usage ### Basic Fragment Usage ```html

Product Dashboard

{product.name}

{product.price}

Items: {cartCount}

Total: {cartTotal}

``` ### Server-Side Fragment Control ```php use App\Framework\LiveComponents\Attributes\Fragment; final class ProductDashboard extends LiveComponent { #[LiveProp] public array $products = []; #[LiveProp] public int $cartCount = 0; #[LiveProp] public float $cartTotal = 0.0; // Update only product list fragment #[LiveAction] #[Fragment('product-list')] public function filterProducts(string $category): void { $this->products = $this->productService->filterByCategory($category); // Cart and preferences remain unchanged } // Update only cart fragment #[LiveAction] #[Fragment('cart')] public function addToCart(string $productId): void { $this->cart->add($productId); $this->cartCount = $this->cart->count(); $this->cartTotal = $this->cart->total(); // Product list and preferences remain unchanged } // Update multiple fragments #[LiveAction] #[Fragment(['product-list', 'cart'])] public function clearCart(): void { $this->cart->clear(); $this->cartCount = 0; $this->cartTotal = 0.0; $this->loadRecommendedProducts(); // Affects product list } } ``` ### Nested Fragments ```html

Analytics Dashboard

Active Users: {activeUsers}

New Signups: {newSignups}

Today's Revenue: {todayRevenue}

Monthly Revenue: {monthlyRevenue}

``` ```php // Update parent fragment (updates everything) #[LiveAction] #[Fragment('dashboard')] public function refreshAll(): void { $this->updateUserStats(); $this->updateRevenueStats(); } // Update specific child fragment #[LiveAction] #[Fragment('user-stats')] public function refreshUserStats(): void { $this->activeUsers = $this->analyticsService->getActiveUsers(); $this->newSignups = $this->analyticsService->getNewSignups(); } ``` ### Dynamic Fragment Names ```html
{todo.title}
``` ```php #[LiveAction] #[Fragment] // Fragment name computed dynamically public function toggleTodo(string $id): void { $todo = $this->findTodo($id); $todo->toggle(); // Set fragment name dynamically $this->setFragmentName("todo-{$id}"); } ``` ### Fragment Events ```javascript // Listen for fragment updates window.addEventListener('livecomponent:fragment-updated', (e) => { const { fragmentName, duration, nodesChanged } = e.detail; console.log(`Fragment "${fragmentName}" updated:`); console.log(`- Duration: ${duration}ms`); console.log(`- Nodes changed: ${nodesChanged}`); // Trigger custom logic after specific fragment updates if (fragmentName === 'cart') { updateCartBadge(); } }); ``` --- ## Request Batching ### Overview Request batching automatically combines multiple rapid actions into a single HTTP request for network efficiency. **Benefits**: - 80-95% reduction in HTTP requests - Lower server load - Better mobile performance - Reduced latency ### Automatic Batching ```javascript // Configure batching globally LiveComponent.configure({ batchSize: 10, // Max actions per batch batchDebounce: 50 // Wait 50ms for more actions }); ``` **How it works**: ``` User clicks button 1 → Queue action User clicks button 2 → Queue action } 50ms debounce window User clicks button 3 → Queue action ↓ Single batched request with 3 actions ``` ### Manual Batch Control ```javascript // Manually flush batch immediately LiveComponent.flushBatch('component-id'); // Pause batching temporarily LiveComponent.pauseBatching(); // Resume batching LiveComponent.resumeBatching(); ``` ### Batch-Aware Action Design ```php // Actions designed for batching efficiency final class ProductGrid extends LiveComponent { #[LiveAction] public function quickView(string $productId): void { // Fast, stateless - perfect for batching $this->activeProductId = $productId; } #[LiveAction] public function addToWishlist(string $productId): void { // Independent operation - batches well $this->wishlist->add($productId); } #[LiveAction] public function updateFilters(array $filters): void { // Depends on state - still batches, processed in order $this->filters = $filters; $this->applyFilters(); } } ``` ### Batch Response Handling ```javascript // Monitor batch efficiency window.addEventListener('livecomponent:batch-sent', (e) => { const { actionsCount, payloadSize } = e.detail; const requestsSaved = actionsCount - 1; const efficiency = (requestsSaved / actionsCount) * 100; console.log(`Batch sent: ${actionsCount} actions`); console.log(`Requests saved: ${requestsSaved}`); console.log(`Efficiency: ${efficiency.toFixed(1)}%`); }); // Handle batch response window.addEventListener('livecomponent:batch-completed', (e) => { const { results, duration } = e.detail; // Process individual action results results.forEach((result, index) => { if (!result.success) { console.error(`Action ${index} failed:`, result.error); } }); }); ``` ### Conditional Batching ```php use App\Framework\LiveComponents\Attributes\NoBatch; final class CriticalAction extends LiveComponent { // Disable batching for critical actions #[LiveAction] #[NoBatch] public function processPayment(): void { // Always sent as standalone request $this->paymentService->charge($this->amount); } // Allow batching (default) #[LiveAction] public function updateQuantity(string $productId, int $qty): void { $this->cart->updateQuantity($productId, $qty); } } ``` --- ## Server-Sent Events (SSE) ### Overview Server-Sent Events enable real-time, server-to-client updates without polling. **Benefits**: - Real-time updates without polling - Lower bandwidth than WebSockets for one-way communication - Automatic reconnection - HTTP/2 multiplexing support ### Basic SSE Usage ```php final class LiveDashboard extends LiveComponent { #[LiveProp] public int $activeUsers = 0; #[LiveProp] public array $recentActivity = []; public function mount(): void { // Enable SSE for this component $this->enableSse(); // Subscribe to specific channels $this->subscribeTo([ 'user.activity', 'system.notifications', "team.{$this->teamId}.updates" ]); } public function unmount(): void { // Clean up SSE connection $this->disableSse(); } } ``` ### Broadcasting Updates ```php use App\Framework\LiveComponents\SSE\SseBroadcaster; final class UserActivityService { public function __construct( private readonly SseBroadcaster $broadcaster ) {} public function recordLogin(User $user): void { $this->repository->recordLogin($user); // Broadcast to all listening components $this->broadcaster->broadcast('user.activity', [ 'type' => 'login', 'user_id' => $user->id, 'username' => $user->name, 'timestamp' => time() ]); } } ``` ### Client-Side SSE Handling ```javascript // Listen for SSE updates window.addEventListener('livecomponent:sse-message', (e) => { const { channel, data } = e.detail; console.log(`SSE message on "${channel}":`, data); // Handle specific event types if (data.type === 'login') { showNotification(`${data.username} logged in`); } }); // Connection status window.addEventListener('livecomponent:sse-connected', (e) => { console.log('SSE connected:', e.detail.componentId); showConnectionStatus('connected'); }); window.addEventListener('livecomponent:sse-disconnected', (e) => { console.warn('SSE disconnected:', e.detail.reason); showConnectionStatus('disconnected'); }); ``` ### SSE with Automatic Component Updates ```php // Server automatically triggers component re-render public function broadcastUpdate(string $message): void { $this->broadcaster->broadcast("component.{$this->id}.update", [ 'trigger_render' => true, // Automatic re-render 'message' => $message ]); } ``` ### Connection Pooling ```php use App\Framework\LiveComponents\SSE\SseConnectionPool; final class SseOptimizedComponent extends LiveComponent { public function mount(): void { // Reuse existing connection pool $this->ssePool->subscribe( componentId: $this->id, channels: ['global.updates'] ); } } ``` --- ## Optimistic UI Updates ### Overview Optimistic UI updates the interface immediately before server confirmation for instant perceived performance. **Benefits**: - <50ms perceived latency - Better user experience - Higher engagement - Network-independent responsiveness ### Simple Optimistic Updates ```html ``` ### Server-Side Optimistic Configuration ```php use App\Framework\LiveComponents\Attributes\Optimistic; final class PostInteractions extends LiveComponent { #[LiveProp] public bool $isLiked = false; #[LiveProp] public int $likeCount = 0; #[LiveAction] #[Optimistic(property: 'isLiked', operation: 'toggle')] #[Optimistic(property: 'likeCount', operation: 'increment')] public function toggleLike(): void { // Client already updated UI optimistically // Server just persists the change $this->isLiked = !$this->isLiked; $this->likeCount += $this->isLiked ? 1 : -1; $this->postService->toggleLike($this->postId, $this->isLiked); } } ``` ### Custom Optimistic Operations ```php #[LiveAction] #[Optimistic(property: 'status', operation: 'custom', handler: 'optimisticStatusChange')] public function changeStatus(string $newStatus): void { $this->status = $newStatus; $this->statusService->update($this->id, $newStatus); } // Custom optimistic handler public function optimisticStatusChange(string $newStatus): mixed { // Return optimistic value return match($newStatus) { 'pending' => 'Processing...', 'approved' => 'Approved ✓', 'rejected' => 'Rejected ✗', default => $newStatus }; } ``` ### Error Handling & Rollback ```php use App\Framework\LiveComponents\Exceptions\OptimisticConflictException; #[LiveAction] #[Optimistic(property: 'quantity', operation: 'set')] public function updateQuantity(int $newQuantity): void { // Check for conflicts $currentStock = $this->inventoryService->getStock($this->productId); if ($newQuantity > $currentStock) { // Rollback optimistic update throw new OptimisticConflictException( "Insufficient stock. Available: {$currentStock}" ); } $this->quantity = $newQuantity; $this->cartService->updateQuantity($this->productId, $newQuantity); } ``` ### Client-Side Rollback Handling ```javascript // Handle optimistic rollback window.addEventListener('livecomponent:optimistic-rollback', (e) => { const { action, error, stateBeforeOptimistic } = e.detail; // Show error to user showNotification(error, 'error'); // Optional: Custom recovery if (action === 'updateQuantity') { highlightField('quantity-input'); } }); ``` ### Conditional Optimistic Updates ```php #[LiveAction] #[Optimistic(property: 'votes', operation: 'increment', condition: 'canVote')] public function upvote(): void { if (!$this->canVote()) { throw new UnauthorizedException('Already voted'); } $this->votes++; $this->voteService->record($this->postId, $this->userId); } private function canVote(): bool { return !$this->hasVoted($this->userId); } ``` --- ## Chunked File Uploads ### Overview Chunked uploads split large files into smaller pieces for reliable, resumable uploads with progress tracking. **Benefits**: - Upload large files (GB+) - Pause/resume support - Progress tracking - Retry failed chunks - Validation before upload ### Basic Upload Configuration ```html
{uploadProgress}%
``` ### Server-Side Upload Handling ```php use App\Framework\LiveComponents\Attributes\LiveAction; use App\Framework\LiveComponents\Upload\ChunkUploadHandler; final class FileUploadComponent extends LiveComponent { #[LiveProp] public float $uploadProgress = 0.0; #[LiveProp] public ?string $uploadId = null; #[LiveProp] public bool $uploadPaused = false; #[LiveAction] public function handleFileUpload( string $uploadId, int $chunkIndex, int $totalChunks, string $filename, string $chunkData ): void { $this->uploadId = $uploadId; // Store chunk $this->chunkUploadHandler->storeChunk( uploadId: $uploadId, chunkIndex: $chunkIndex, data: base64_decode($chunkData) ); // Update progress $this->uploadProgress = ($chunkIndex + 1) / $totalChunks * 100; // Last chunk - assemble file if ($chunkIndex === $totalChunks - 1) { $finalPath = $this->chunkUploadHandler->assembleFile( uploadId: $uploadId, filename: $filename, totalChunks: $totalChunks ); $this->processUploadedFile($finalPath); } } #[LiveAction] public function pauseUpload(): void { $this->uploadPaused = true; } #[LiveAction] public function resumeUpload(): void { $this->uploadPaused = false; } #[LiveAction] public function cancelUpload(): void { if ($this->uploadId) { $this->chunkUploadHandler->cleanup($this->uploadId); $this->uploadId = null; $this->uploadProgress = 0.0; } } } ``` ### Client-Side Upload Progress ```javascript // Track upload progress window.addEventListener('livecomponent:upload-progress', (e) => { const { uploadId, progress, chunkIndex, totalChunks } = e.detail; console.log(`Upload ${uploadId}: ${progress}%`); console.log(`Chunk ${chunkIndex + 1} of ${totalChunks}`); // Update UI updateProgressBar(progress); }); // Upload complete window.addEventListener('livecomponent:upload-complete', (e) => { const { uploadId, filename, fileSize } = e.detail; showNotification(`Upload complete: ${filename}`); }); // Upload error window.addEventListener('livecomponent:upload-error', (e) => { const { uploadId, error, chunkIndex } = e.detail; console.error(`Upload failed at chunk ${chunkIndex}:`, error); // Retry logic if (e.detail.retryable) { retryUpload(uploadId, chunkIndex); } }); ``` ### Resumable Uploads ```php final class ResumableUploadHandler { public function resumeUpload(string $uploadId): UploadState { // Get upload state from storage $state = $this->uploadStateRepository->find($uploadId); if (!$state) { throw new UploadNotFoundException($uploadId); } // Return chunks that still need uploading $uploadedChunks = $this->getUploadedChunks($uploadId); $remainingChunks = array_diff( range(0, $state->totalChunks - 1), $uploadedChunks ); return new UploadState( uploadId: $uploadId, filename: $state->filename, totalChunks: $state->totalChunks, uploadedChunks: $uploadedChunks, remainingChunks: $remainingChunks ); } } ``` --- ## Component Communication ### Parent-Child Communication ```php // Parent component final class Dashboard extends LiveComponent { #[LiveProp] public array $childData = []; #[LiveAction] public function updateChild(array $data): void { $this->childData = $data; // Broadcast update to child component $this->emit('child-component-id', 'dataUpdated', $data); } } // Child component final class Widget extends LiveComponent { public function mount(): void { // Listen for parent events $this->on('dataUpdated', fn($data) => $this->handleUpdate($data)); } private function handleUpdate(array $data): void { $this->processData($data); } } ``` ### Sibling Communication ```php // Component A #[LiveAction] public function notifySibling(string $message): void { // Broadcast to specific component $this->broadcast('component-b-id', 'messageReceived', [ 'message' => $message, 'sender' => $this->id ]); } // Component B public function mount(): void { $this->on('messageReceived', function($data) { $this->handleMessage($data['message'], $data['sender']); }); } ``` ### Global Events ```php // Broadcast to all components final class GlobalNotification extends LiveComponent { #[LiveAction] public function sendNotification(string $message): void { $this->broadcastGlobal('notification.new', [ 'message' => $message, 'timestamp' => time() ]); } } // Any component can listen public function mount(): void { $this->onGlobal('notification.new', function($data) { $this->notifications[] = $data; }); } ``` --- ## State Management Patterns ### Computed Properties ```php final class ShoppingCart extends LiveComponent { #[LiveProp] public array $items = []; // Computed property (not serialized) public function getSubtotal(): float { return array_reduce( $this->items, fn($total, $item) => $total + ($item['price'] * $item['quantity']), 0.0 ); } public function getTax(): float { return $this->getSubtotal() * 0.19; } public function getTotal(): float { return $this->getSubtotal() + $this->getTax(); } } ``` ### Derived State ```php final class FilteredList extends LiveComponent { #[LiveProp] public array $allItems = []; #[LiveProp] public string $searchTerm = ''; #[LiveProp] public array $filters = []; // Derived state private array $filteredItems = []; public function updated(string $property, mixed $oldValue, mixed $newValue): void { if (in_array($property, ['searchTerm', 'filters'])) { $this->recalculateFilteredItems(); } } private function recalculateFilteredItems(): void { $this->filteredItems = array_filter( $this->allItems, fn($item) => $this->matchesFilters($item) ); } public function render(): string { return $this->view('filtered-list', [ 'items' => $this->filteredItems ]); } } ``` ### State Persistence ```php use App\Framework\LiveComponents\Attributes\Persisted; final class UserPreferences extends LiveComponent { #[LiveProp] #[Persisted(storage: 'session', key: 'user.preferences')] public array $preferences = []; #[LiveAction] public function updatePreference(string $key, mixed $value): void { $this->preferences[$key] = $value; // Automatically persisted to session } } ``` --- ## Custom Actions ### Action Middleware ```php use App\Framework\LiveComponents\Middleware\ActionMiddleware; final class LoggingMiddleware implements ActionMiddleware { public function before( LiveComponent $component, string $action, array $params ): void { $this->logger->info("Action starting: {$action}", [ 'component' => get_class($component), 'params' => $params ]); } public function after( LiveComponent $component, string $action, mixed $result ): void { $this->logger->info("Action completed: {$action}"); } } ``` ### Custom Action Attributes ```php use App\Framework\LiveComponents\Attributes\CustomAction; #[CustomAction( name: 'tracked', handler: TrackingActionHandler::class )] #[LiveAction] public function importantAction(): void { // Action automatically tracked by TrackingActionHandler } ``` --- **Next**: [Best Practices](best-practices/component-design.md) →