Symfony UX Live Component

repository·3.x·Indexed 41 days ago

https://github.com/symfony/ux-live-component

A library for creating reactive, real-time user interfaces by automatically updating Twig components on the frontend. It provides server-side re-rendering and model binding, allowing developers to build interactive experiences similar to Livewire or Phoenix LiveView. Version 3.5.1 includes a Component class API for state management, lifecycle hooks, event emission (emit, emitUp, emitSelf), and a Stimulus controller for configuring behavior via data-live attributes.

Tokens
25.7K
Snippets
80
Records
94
Agent score
50%

What's inside Symfony UX Live Component

  1. Scope events to specific components

    3.x

    By default, emitted events are sent to all components on the page. You can restrict the scope of an event using the following methods:

    • Emit only to Parent Components: Use emitUp() in PHP or data-action="live#emitUp" in Twig.
    • Emit only to Yourself: Use emitSelf() in PHP or data-action="live#emitSelf" in Twig.
    • Emit only to a Specific Component Name:
      • PHP: $this->emit('eventName', componentName: 'ComponentName').
      • Twig: Use the name() modifier in the event parameter: data-live-event-param="name(ComponentName)|eventName".
  2. Use LiveProps for stateful component properties

    3.x

    The #[LiveProp] attribute marks a property as "stateful". When a component re-renders (e.g., via an Ajax call), LiveProps are automatically re-hydrated with their previous values. This allows the component to "remember" user input or configuration across interactions.

    Key behaviors:

    • Statefulness: Without #[LiveProp], properties will not persist their values during a re-render.
    • Services: You do not need to mark services as #[LiveProp] because they are autowired by Symfony each time the component is rendered.
    • Supported Types: LiveProps must be serializable to JavaScript. Supported types include:
      • Scalars (int, float, string, bool, null)
      • Arrays of scalars
      • Enums
      • DateTime objects
      • Doctrine entities
      • DTOs (and arrays of DTOs)

    Example usage:

    #[AsLiveComponent]
    class RandomNumber
    {
        use DefaultActionTrait;
    
        #[LiveProp]
        public int $max = 1000;
    
        public function getRandomNumber(): int
        {
            return rand(0, $this->max);
        }
    }

    Render the component with specific state:

    {{ component('RandomNumber', { max: 500 }) }}
    #[LiveProp]
    public int $max = 1000;
  3. Use Symfony controller features in Live Components

    3.x

    Live components behave like real Symfony controllers. This provides several advantages:

    1. Autowiring: You can autowire services (like LoggerInterface) directly into your #[LiveAction] methods.
    2. Security: You can use standard Symfony security attributes like #[IsGranted] on the entire component class or specific actions.
    3. Redirects: By extending AbstractController, your component can use standard controller shortcuts like $this->redirectToRoute() or $this->addFlash() within your actions.
    4. CSRF Protection: Actions are sent via POST with a custom Accept header that is automatically validated. To maintain security, ensure your CORS configuration does not use Access-Control-Allow-Origin: *.
    // src/Twig/Components/RandomNumber.php
    namespace App\Twig\Components;
    
    use Symfony\Component\Security\Http\Attribute\IsGranted;
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\UX\LiveComponent\Attribute\LiveAction;
    use Psr\Log\LoggerInterface;
    
    class RandomNumber extends AbstractController
    {
        #[LiveAction]
        #[IsGranted('ROLE_USER')]
        public function resetMax(LoggerInterface $logger)
        {
            $this->max = 1000;
            $logger->debug('The min/max were reset!');
            
            $this->addFlash('success', 'Max has been reset!');
            return $this->redirectToRoute('app_random_number');
        }
    }
  4. Passing blocks to Live Components

    3.x

    You can pass content to Live Components using Twig blocks, just like standard Twig Components.

    Important Limitation: Variables defined in the 'outside' template (the caller) are only available during the initial render. During subsequent Ajax re-renders, those variables will be lost. However, variables defined inside the block itself remain available during re-renders.

    {# This will fail during re-render because 'message' is outside the block #}
    {% set message = '...' %}
    {% component Alert %}
        {% block content %}{{ message }}{% endblock %}
    {% endcomponent %}
    
    {# This works because 'message' is defined inside the block #}
    {% component Alert %}
        {% block content %}
            {% set message = 'this works!' %}
            {{ message }}
        {% endblock %}
    {% endcomponent %}
  5. How nested components interact

    3.x

    By default, each Live Component exists in its own isolated universe. A re-render of a parent component does not automatically trigger a re-render of its children, and updates to a child's model do not affect the parent.

    To change this behavior, you can use specific techniques to synchronize state or trigger re-renders across the parent-child boundary.

  6. Use Doctrine entities as LiveProps

    3.x

    You can use Doctrine entities as #[LiveProp] values.

    • Persisted entities: Dehydrated to their ID and hydrated back by querying the database.
    • Unpersisted entities: Dehydrated to an empty array and hydrated back as a new instance (e.g., new Post()).
    • Collections: You can support arrays of entities (e.g., Post[]) by providing proper PHPDoc type hints. This requires the phpdocumentor/reflection-docblock library.
    use App\Entity\Post;
    
    #[AsLiveComponent]
    class EditPost
    {
        #[LiveProp]
        public Post $post;
    }
  7. Download files from a LiveAction

    3.x

    As of version 3.5, you can trigger file downloads from a LiveAction by returning a LiveResponse. The component will still re-render, allowing state changes to persist on the page while the browser handles the download.

    Best Practices

    • Use LiveResponse::downloadUrl() whenever possible. This points the browser to a dedicated route. It is more efficient as it uses native browser downloading, supports range requests/resuming, and doesn't consume server memory for the file content.
    • Use LiveResponse::downloadFile() only when a dedicated URL cannot exist, such as when the action generates the file content dynamically.

    Constraints

    • You can only return a LiveResponse from a LiveAction or a LiveListener via a POST request.
    • Do not return a LiveResponse from the default action (which runs on every re-render) or from a GET request.
    • An action can return either a LiveResponse or a redirect, but not both.
  8. Style Loading States with `aria-busy`

    3.x

    While a component is re-rendering or an action is being processed, Live Components automatically set the aria-busy="true" attribute on the component's root element. This is useful for both accessibility and CSS styling.

    Accessibility

    Assistive technologies can use this attribute to announce that a region is busy.

    CSS Styling

    You can target the loading state directly in your stylesheets.

    Plain CSS:

    [aria-busy="true"] {
        opacity: 0.5;
        pointer-events: none;
    }

    Tailwind CSS:

    <div class="aria-busy:opacity-50">...</div>
    <div {{ attributes }} class="aria-busy:opacity-50">
        ...
    </div>
  9. Use DTOs with LiveProps

    3.x

    You can use Data Transfer Objects (DTOs) as LiveProp values.

    Requirements:

    • The property must have the correct type hint.
    • The DTO cannot have constructor arguments.
    • It uses Symfony's PropertyAccess component, so it supports both public properties and getter/setter methods.
    • For collections of DTOs, use PHPDoc (e.g., /** @var AddressDto[] */).

    Hydration Strategies:

    1. Default: Uses public properties or getter/setter methods.
    2. Serializer: Use #[LiveProp(useSerializerForHydration: true)] to use Symfony's Serializer. You can also provide a serializationContext.
    3. Manual Methods: Use hydrateWith and dehydrateWith to specify custom method names for the process.
    4. Hydration Extensions: Implement HydrationExtensionInterface for global, type-based custom logic.
    // Option 1: Manual Methods
    #[LiveProp(hydrateWith: 'hydrateAddress', dehydrateWith: 'dehydrateAddress')]
    public AddressDto $addressDto;
    
    public function dehydrateAddress(AddressDto $address): array
    {
        return ['street' => $address->street, 'city' => $address->city];
    }
    
    public function hydrateAddress(array $data): AddressDto
    {
        return new AddressDto($data['street'], $data['city']);
    }
  10. Use the id attribute and key prop for stable morphing

    3.x

    The id attribute (and the key prop for child components) acts as a unique identifier that helps the morphing library connect existing HTML with new HTML during a re-render.

    To preserve an element's state or a child component's instance, you must keep the server-rendered id or key stable. If the id or key changes on the server, the element/component is replaced entirely, including its JavaScript controllers and client-side state.

  11. Backward Compatibility Promise for Live Components

    3.x

    The Live Components bundle follows the standard Symfony Backward Compatibility (BC) promise.

    For JavaScript files, the public API (documented features and exports from the main JavaScript file) is protected by the BC promise. However, internal implementation details (exports from internal files) are not protected and may change without notice.