# Symfony UX Live Components ## Introduction Symfony UX Live Components is a powerful PHP library that enables automatic frontend updates for Twig components as users interact with them, inspired by Laravel Livewire and Phoenix LiveView. The bundle integrates seamlessly with Symfony's TwigComponent library to create reactive, stateful components that update without full page refreshes. Components maintain their state through LiveProps, which can be serialized and sent to the frontend, then hydrated back on the server during AJAX re-renders. The library provides a sophisticated system for building interactive UIs with minimal JavaScript, handling form submissions, validation, event broadcasting between components, and deferred/lazy loading. It includes TypeScript frontend controllers using Stimulus, automatic model binding for form inputs, lifecycle hooks for component hydration/dehydration, and seamless integration with Symfony forms and Doctrine entities. The architecture supports parent-child component relationships, URL-bound state, and polling for real-time updates. ## Core APIs and Functions ### Creating a Basic Live Component Define a live component using the `#[AsLiveComponent]` attribute with reactive properties marked by `#[LiveProp]`. ```php items = ['Buy groceries', 'Write code', 'Deploy app']; } } ``` ### Live Component Template with Model Binding Create a Twig template that automatically updates on user interaction using `data-model` attributes. ```twig
``` ### Component with Actions Define interactive actions using `#[LiveAction]` attribute for user-triggered operations. ```php count++; } #[LiveAction] public function decrement(): void { $this->count--; } #[LiveAction] public function reset(): void { $this->count = 0; } } ``` ```twig

Count: {{ count }}

``` ### Form Integration with ComponentWithFormTrait Integrate Symfony forms into live components with automatic validation and submission handling. ```php createForm(UserFormType::class, $this->user); } #[LiveAction] public function save(): void { $this->submitForm(); $form = $this->getForm(); if ($form->isValid()) { $entityManager = $this->getDoctrine()->getManager(); $entityManager->persist($this->user); $entityManager->flush(); $this->addFlash('success', 'User saved successfully!'); } } } ``` ```twig
{{ form_start(form) }} {{ form_row(form.name) }} {{ form_row(form.email) }} {{ form_end(form) }}
``` ### Validation with ValidatableComponentTrait Add real-time validation to components using Symfony's validator constraints. ```php validate(); // Send email or save to database // ... } } ``` ```twig
{% if _errors.has('name') %} {{ _errors.get('name').message }} {% endif %}
{% if _errors.has('email') %} {{ _errors.get('email').message }} {% endif %}
{% if _errors.has('message') %} {{ _errors.get('message').message }} {% endif %}
``` ### LiveProp Configuration Options Configure LiveProp behavior with writable paths, custom hydration, and URL binding. ```php ['min' => 0, 'max' => 1000], 'inStock' => true, ]; // Custom hydration/dehydration #[LiveProp(hydrateWith: 'hydrateProduct', dehydrateWith: 'dehydrateProduct')] public ?Product $selectedProduct = null; // DateTime with specific format #[LiveProp(writable: true, format: 'Y-m-d')] public ?\DateTimeInterface $startDate = null; // Hook called after property update #[LiveProp(writable: true, onUpdated: 'onQueryChanged')] public string $searchQuery = ''; public function hydrateProduct(int $id): ?Product { return $this->productRepository->find($id); } public function dehydrateProduct(?Product $product): ?int { return $product?->getId(); } public function onQueryChanged(string $oldValue): void { $this->page = 1; // Reset to first page on new search } } ``` ### Event Broadcasting Between Components Emit and listen to events for inter-component communication. ```php items[$productId])) { $this->items[$productId]['quantity'] += $quantity; } else { $this->items[$productId] = [ 'id' => $productId, 'quantity' => $quantity, ]; } } #[LiveListener('cart:clear')] public function clearCart(): void { $this->items = []; } } #[AsLiveComponent('product_item')] final class ProductItemComponent { use DefaultActionTrait; #[LiveProp] public int $productId; #[LiveAction] public function addToCart(): void { $this->emit('product:added', [ 'productId' => $this->productId, 'quantity' => 1, ]); } } ``` ```twig {# Product item template #}
{# Or emit directly from template #}
``` ### Lifecycle Hooks Use lifecycle hooks to execute code at specific points in the component lifecycle. ```php userData['lastLogin'] = $this->userRepository->getLastLogin(); } /** * Called before component state is serialized to frontend */ #[PreDehydrate] public function preDehydrate(): void { // Remove sensitive data before sending to frontend unset($this->userData['sensitiveField']); } /** * Called before re-rendering (only on AJAX requests) */ #[PreReRender] public function preReRender(): void { // Fetch fresh data before each re-render if ($this->filter) { $this->cachedResults = $this->searchService->search($this->filter); } } public function getFilteredResults(): array { return $this->cachedResults; } } ``` ### Frontend JavaScript Integration Access component functionality from JavaScript using the Stimulus controller. ```javascript // Import the controller import { Controller } from '@hotwired/stimulus'; import { getComponent } from '@symfony/ux-live-component'; export default class extends Controller { connect() { // Get the component instance const component = getComponent(this.element); // Listen to component events this.element.addEventListener('live:connect', (event) => { console.log('Component connected', event.detail.component); }); this.element.addEventListener('live:render:finished', (event) => { console.log('Component re-rendered'); }); // Update model programmatically component.set('searchTerm', 'New value', true); // true = should render // Call an action component.action('save', { arg1: 'value' }); // Emit events component.emit('custom:event', { data: 'value' }); component.emitUp('bubble:event', { data: 'value' }); component.emitSelf('self:event', { data: 'value' }); } } ``` ### Model Directive Modifiers Control model binding behavior with directive modifiers for debouncing and event targeting. ```twig
{# Update on input with default debounce #} {# Update only on change event #} {# Update on blur event #} {# Custom debounce (milliseconds) #} {# No debounce #} {# Update without re-rendering #} {# Minimum/maximum length before triggering #} {# Numeric constraints #} {# Wildcard model for forms - binds all fields automatically #}
``` ### Lazy and Deferred Loading Load components lazily or defer rendering for better performance. ```twig {# Lazy load - renders when scrolled into view #}
{{ component('heavy_component', { data: complexData }) }}
{# Deferred loading - shows placeholder, then loads #}
Loading...
{# Deferred with custom placeholder #} {{ component('heavy_chart', { data: chartData }, { loading: 'defer', loading_template: 'components/_chart_loading.html.twig' }) }} {# Polling - refresh every N milliseconds #}
Last updated: {{ "now"|date('H:i:s') }}
``` ### Loading States and Indicators Add loading states to actions and model updates for better UX. ```twig
{# Disable during any component update #} {# Show spinner during specific action #}
Saving...
{# Multiple loading states #}
``` ### Component Configuration and Routing Configure component behavior and customize routing. ```php add('ux_live_component', '/_components/{_live_component}/{_live_action}') ->defaults([ '_live_action' => 'get', ]); // Custom route for specific component $routes->add('app_custom_live_route', '/api/live/{_live_component}/{_live_action}') ->defaults([ '_live_action' => 'get', ]); }; ``` ## Summary and Use Cases Symfony UX Live Components excels in scenarios requiring interactive user interfaces without the complexity of a full JavaScript framework. Common use cases include real-time search interfaces with instant filtering, multi-step forms with progressive validation, shopping carts that update without page refreshes, live dashboards with polling updates, and dynamic form collections that expand or contract based on user input. The library shines when building admin panels, wizard-style interfaces, inline editing functionality, and components that need to communicate through events like notification systems or collaborative features. Integration patterns typically involve server-side PHP components that extend Symfony controllers, leveraging traits like `ComponentWithFormTrait` for form handling and `ValidatableComponentTrait` for validation. Frontend integration uses Stimulus controllers with data attributes for model binding (`data-model`), action triggering (`data-action="live#action"`), and event emission (`data-action="live#emit"`). The architecture supports nested child components, URL state synchronization for bookmarkable interfaces, custom hydration strategies for complex entities, and seamless Doctrine ORM integration. Advanced patterns include implementing optimistic UI updates, handling file uploads, managing loading states with granular control, and building real-time collaborative features using event broadcasting between multiple component instances.