# 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
```
### 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) →