All skills
smnandre avatar

/live-component

@6d7ef8e

Symfony UX LiveComponent for reactive server-rendered UI -- components that re-render via AJAX on user interaction, zero JavaScript required. Use when building live search, real-time filtering, dynamic forms, inline validation, dependent selects, auto-save, polling, deferred/lazy rendering, or any UI that updates itself based on user input. Code triggers: AsLiveComponent, LiveProp, LiveAction, data-model, data-loading, data-live-action-url, ComponentWithFormTrait, LiveListener, emit, defer, lazy, polling. Also trigger when the user asks "how to build a search that filters as I type", "how to validate a form in real-time", "how to make a reactive component in PHP", "how to build dependent selects", "how to defer component rendering", "how to communicate between components via emit", "how to bind a form to a LiveComponent". Do NOT trigger for static reusable UI without reactivity (use twig-component), pure client-side JS behavior (use stimulus), or page-level navigation (use turbo).

Use this Skill: https://skilld.dev/gh/smnandre/symfony-ux-skills/live-component

This session only. Nothing lands on disk.

referencesgotchas.md

≈2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

LiveComponent Gotchas & Debugging

Common pitfalls and solutions.


Props Not Persisting

Non-LiveProp Values Reset

// ❌ Regular property - resets on every re-render
public string $query = '';

// ✅ LiveProp - persists between re-renders
#[LiveProp]
public string $query = '';

Props Must Be Writable for data-model

// ❌ Not writable - data-model won't work
#[LiveProp]
public string $search = '';

// ✅ Writable - data-model works
#[LiveProp(writable: true)]
public string $search = '';

Object Props Need Hydration

// ❌ Complex objects can't auto-hydrate
#[LiveProp]
public ComplexDTO $data;

// ✅ Entity with ID - auto-hydrates
#[LiveProp]
public Product $product;

// ✅ Custom hydration for DTOs
#[LiveProp(hydrateWith: 'hydrateData', dehydrateWith: 'dehydrateData')]
public ComplexDTO $data;

Missing DefaultActionTrait

// ❌ Won't work - missing trait
#[AsLiveComponent]
final class MyComponent
{
}

// ✅ Must include DefaultActionTrait
#[AsLiveComponent]
final class MyComponent
{
    use DefaultActionTrait;
}

Missing attributes in Template

{# ❌ Component won't work - missing attributes #}
<div>
    Content
</div>

{# ✅ Root element must have {{ attributes }} #}
<div {{ attributes }}>
    Content
</div>

data-model Binding Issues

Wrong Element for Binding

{# ❌ data-model on non-input element #}
<div data-model="query">...</div>

{# ✅ data-model on input/select/textarea #}
<input data-model="query">
<select data-model="category">
<textarea data-model="content">

Nested Property Access

#[LiveProp(writable: ['email', 'name'])]
public User $user;
{# ✅ Access nested properties #}
<input data-model="user.email">
<input data-model="user.name">

{# ❌ Property not in writable list #}
<input data-model="user.password">

Checkbox/Radio Binding

{# Checkbox needs value or boolean prop #}
<input type="checkbox" data-model="enabled">

{# Multiple checkboxes to array #}
<input type="checkbox" data-model="selectedIds" value="1">
<input type="checkbox" data-model="selectedIds" value="2">

Action Errors

Action Not Found

// ❌ Missing LiveAction attribute
public function save(): void { }

// ✅ Must have LiveAction attribute
#[LiveAction]
public function save(): void { }

Wrong Parameter Name

{# Template #}
<button 
    data-action="live#action"
    data-live-action-param="delete"
    data-live-id-param="{{ id }}"  {# param name: id #}
>
// ❌ Param name mismatch
#[LiveAction]
public function delete(#[LiveArg] int $itemId): void { }

// ✅ Matching param name
#[LiveAction]
public function delete(#[LiveArg] int $id): void { }

// ✅ Or specify custom name
#[LiveAction]
public function delete(#[LiveArg('id')] int $itemId): void { }

Form Issues

Form Not Validating

// ❌ Form not submitted
#[LiveAction]
public function save(): void
{
    $data = $this->getForm()->getData();  // Not validated!
}

// ✅ Submit form first
#[LiveAction]
public function save(): void
{
    $this->submitForm();  // Validates
    
    if (!$this->getForm()->isValid()) {
        return;  // Re-renders with errors
    }
    
    $data = $this->getForm()->getData();
}

initialFormData Type Mismatch

// ❌ Wrong type for initialFormData
#[LiveProp]
public ?array $initialFormData = null;

// ✅ Match form's data_class
#[LiveProp]
public ?User $initialFormData = null;

Form Re-creation Issues

protected function instantiateForm(): FormInterface
{
    // ❌ Creating form with data from prop that changes
    return $this->createForm(UserType::class, $this->user);
    
    // ✅ Use initialFormData for initial state
    return $this->createForm(UserType::class, $this->initialFormData);
}

Hydration Errors

Entity Not Found

#[LiveProp]
public Product $product;  // ❌ If product deleted, hydration fails

Solution: Make nullable or handle in mount:

#[LiveProp]
public ?Product $product = null;

public function mount(?Product $product): void
{
    $this->product = $product;
}

Collection Hydration

// ❌ Array of entities won't auto-hydrate
#[LiveProp]
public array $products = [];

// ✅ Store IDs and fetch in getter
#[LiveProp]
public array $productIds = [];

public function getProducts(): array
{
    return $this->productRepository->findBy(['id' => $this->productIds]);
}

Performance Issues

Too Many Re-renders

{# ❌ Re-renders on every keystroke #}
<input data-model="search">

{# ✅ Debounce to reduce requests #}
<input data-model="debounce(300)|search">

{# ✅ Only update on blur #}
<input data-model="on(blur)|search">

Heavy Computed Properties

// ❌ Expensive query on every render
public function getProducts(): array
{
    return $this->repository->findWithComplexQuery();
}

// ✅ Use computed caching
{# Computed is cached within single render #}
{% for product in computed.products %}

Large DOM Updates

{# ❌ Large list without stable IDs #}
{% for item in items %}
    <div>{{ item.name }}</div>
{% endfor %}

{# ✅ Add data-live-id for efficient morphing #}
{% for item in items %}
    <div data-live-id="{{ item.id }}">{{ item.name }}</div>
{% endfor %}

DOM Preservation

Third-Party Widgets Destroyed

{# ❌ Widget re-initialized on every render #}
<select class="select2">

{# ✅ Prevent morphing #}
<div data-live-ignore>
    <select class="select2">
</div>

User Input Lost

{# ❌ Input value reset during morph #}
<input type="text" name="temp">

{# ✅ Preserve specific attribute #}
<input type="text" name="temp" data-live-preserve="value">

Event Issues

Event Not Received

// Child emits
$this->emit('item:saved', ['id' => 1]);

// ❌ Parent listener method name mismatch
#[LiveListener('itemSaved')]  // Wrong event name
public function onItemSaved(): void { }

// ✅ Exact event name match
#[LiveListener('item:saved')]
public function onItemSaved(#[LiveArg] int $id): void { }

emitUp vs emit

// emit - broadcasts to all listeners
$this->emit('event');

// emitUp - only to parent components
$this->emitUp('event');

// emitTo - to specific component
$this->emitTo('ComponentName', 'event');

URL Sync Issues

URL Not Updating

// ❌ Missing writable
#[LiveProp(url: true)]
public string $query = '';

// ✅ Must be writable for URL sync
#[LiveProp(writable: true, url: true)]
public string $query = '';

History State

// Default: replaces URL without history entry
#[LiveProp(writable: true, url: true)]

// Push to history (back button works)
use Symfony\UX\LiveComponent\Metadata\UrlMapping;

#[LiveProp(writable: true, url: new UrlMapping(history: 'push'))]

Debugging

Enable Debug Mode

Check browser console for LiveComponent logs.

Inspect Component State

{# Debug: show current state #}
<pre>{{ _context|json_encode(constant('JSON_PRETTY_PRINT')) }}</pre>

Network Tab

  1. Open DevTools → Network
  2. Filter by Fetch/XHR
  3. Look for requests to /_components/
  4. Check request payload for props
  5. Check response for HTML

Common Errors

Error Cause
"Component not found" Missing #[AsLiveComponent] or not in configured namespace
"Checksum mismatch" LiveProp changed without being writable
"Cannot hydrate" Entity deleted or custom type without hydrator
"Action not found" Missing #[LiveAction] attribute

Test in Isolation

use Symfony\UX\LiveComponent\Test\InteractsWithLiveComponents;

public function testComponent(): void
{
    $component = $this->createLiveComponent('MyComponent', [
        'initialProp' => 'value'
    ]);
    
    // Debug
    dump($component->render());
    dump($component->get('propName'));
}

Source: SKILL.md on GitHub

No alerts15d4 checks · Risk SAFE
  • Gen Agent Trust Hub15d

    This skill provides documentation and code examples for using Symfony UX LiveComponents to build reactive PHP applications. No security risks or malicious behaviors were detected.

  • Socket15d

    No alerts

  • Snyk15d

    Risk: LOW · No issues

  • Runlayer7mo

    4 files scanned · No issues

Signed by skilld at 6d7ef8e. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 4 months ago
Other metadata
metadata
{
  "author": "Simon Andre",
  "email": "smn.andre@gmail.com",
  "url": "https://smnandre.dev",
  "version": "1.2.0"
}

README badge

README badge for smnandre/symfony-ux-skills/live-component