Skip to content

Commit 3b326f6

Browse files
committed
[Live] Adding more detail about dealing with loops
1 parent 6df9d6b commit 3b326f6

File tree

1 file changed

+138
-2
lines changed

1 file changed

+138
-2
lines changed

src/LiveComponent/doc/index.rst

Lines changed: 138 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2631,6 +2631,23 @@ Notice that ``MarkdownTextarea`` allows a dynamic ``name``
26312631
attribute to be passed in. This makes that component re-usable in any
26322632
form.
26332633

2634+
Rendering Quirks with List of Elements
2635+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2636+
2637+
If you're rendering a list of elements in your component, to help LiveComponents
2638+
understand which element is which between re-renders (i.e. if something re-orders
2639+
or removes some of those elements), you can add a ``data-live-id`` attribute to
2640+
each element
2641+
2642+
.. code-block:: twig
2643+
2644+
{# templates/components/Invoice.html.twig #}
2645+
{% for lineItem in lineItems %}
2646+
<div data-live-id="{{ lineItem.id }}">
2647+
{{ lineItem.name }}
2648+
</div>
2649+
{% endfor %}
2650+
26342651
Rendering Quirks with List of Embedded Components
26352652
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26362653

@@ -2650,8 +2667,8 @@ to that component:
26502667
26512668
{# templates/components/Invoice.html.twig #}
26522669
{% for lineItem in lineItems %}
2653-
{{ component('invoice_line_item', {
2654-
productId: lineItem.productId,
2670+
{{ component('InvoiceLineItem', {
2671+
lineItem: lineItem,
26552672
key: lineItem.id,
26562673
}) }}
26572674
{% endfor %}
@@ -2661,6 +2678,125 @@ which will be used to identify each child component. You can
26612678
also pass in a ``data-live-id`` attribute directly, but ``key`` is
26622679
a bit more convenient.
26632680

2681+
Tricks with a Loop + a "New" Item
2682+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2683+
2684+
Let's get fancier. Suppose you're rendering all of the line items
2685+
inside of an `InvoiceCreator` component. After the loop, you might
2686+
decide to render one more component to create a *new* line item.
2687+
In that case, you can pass in a ``key`` set to something like ``new_line_item``:
2688+
2689+
.. code-block:: twig
2690+
2691+
{{ component('InvoiceLineItem', {
2692+
key: 'new_line_item',
2693+
}) }}
2694+
2695+
Imagine you also have a ``LiveAction`` inside of ``InvoiceLineItem``
2696+
that saves the new line item to the database. To be extra fancy,
2697+
it emits a ``lineItem:created`` event to the parent::
2698+
2699+
// src/Twig/InvoiceLineItem.php
2700+
// ...
2701+
2702+
#[AsLiveComponent]
2703+
final class InvoiceLineItem
2704+
{
2705+
// ...
2706+
2707+
#[LiveProp]
2708+
#[Valid]
2709+
public InvoiceLineItem $lineItem;
2710+
2711+
#[PostMount]
2712+
public function postMount(): void
2713+
{
2714+
if(!$this->lineItem) {
2715+
$this->lineItem = new InvoiceLineItem();
2716+
}
2717+
}
2718+
2719+
#[LiveAction]
2720+
public function save(EntityManagerInterface $entityManager)
2721+
{
2722+
if (!$this->lineItem->getId()) {
2723+
$this->emit('lineItem:created', $this->lineItem);
2724+
}
2725+
2726+
$entityManager->persist($this->lineItem);
2727+
$entityManager->flush();
2728+
}
2729+
}
2730+
2731+
Finally, the parent ``InvoiceCreator`` component listens to this
2732+
so that it can re-render the line items (which will now contain the
2733+
newly-saved item)::
2734+
2735+
// src/Twig/InvoiceCreator.php
2736+
// ...
2737+
2738+
#[AsLiveComponent]
2739+
final class InvoiceCreator
2740+
{
2741+
// ...
2742+
2743+
#[LiveListener('lineItem:created')]
2744+
public function addLineItem()
2745+
{
2746+
// no need to do anything here: the component will re-render
2747+
}
2748+
}
2749+
2750+
This will work beautifully: when a new line item is saved, the ``InvoiceCreator``
2751+
component will re-render and the newly saved line item will be displayed along
2752+
with the extra ``new_line_item`` component at the bottom.
2753+
2754+
But something surprising might happen: the ``new_line_item`` component won't
2755+
update! It will *keep* the data and props that were there a moment ago (i.e. the
2756+
form fields will still have data in them) instead of rendering a fresh, empty component.
2757+
2758+
Why? When live components re-renders, it thinks the existing ``key: new_line_item``
2759+
component on the page is the *same* new component that it's about to render. And
2760+
because the props passed into that component haven't changed, it doesn't see any
2761+
reason to re-render it.
2762+
2763+
To fix this, you haev two options:
2764+
2765+
1. Make the ``key`` dynamic so it will be different after adding a new item::
2766+
2767+
.. code-block:: twig
2768+
2769+
{{ component('InvoiceLineItem', {
2770+
key: 'new_line_item_' lineItems|length,
2771+
}) }}
2772+
2773+
2. Reset the state of the ``InvoiceLineItem`` component after it's saved::
2774+
2775+
// src/Twig/InvoiceLineItem.php
2776+
2777+
// ...
2778+
class InvoiceLineItem
2779+
{
2780+
// ...
2781+
2782+
#[LiveAction]
2783+
public function save(EntityManagerInterface $entityManager)
2784+
{
2785+
$isNew = null === $this->lineItem->getId();
2786+
2787+
$entityManager->persist($this->lineItem);
2788+
$entityManager->flush();
2789+
2790+
if ($isNew) {
2791+
// reset the state of this component
2792+
$this->emit('lineItem:created', $this->lineItem);
2793+
$this->lineItem = new InvoiceLineItem();
2794+
// if you're using ValidatableComponentTrait
2795+
$this->clearValidation();
2796+
}
2797+
}
2798+
}
2799+
26642800
Advanced Functionality
26652801
----------------------
26662802

0 commit comments

Comments
 (0)