All components live in their own universe with live-components. But you often want to reload sub-components if data is updated on the parent component.
Imagine the following component:
class VariantForm
{
use ComponentWithFormTrait;
#[LiveProp()]
public Variant $variant;
protected function instantiateForm(): FormInterface
{
return $this->createForm(
VariantType::class,
$this->variant
);
}
}
With a type that rely on options:
class VariantType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('optionValues', OptionsValueType::class, [
'options' => $options['data']->getOptions()
]);
}
}
Those options are usually from a product, so you may have something like this to instanciate the component:
{{ component('VariantForm', {variant: this.newVariant()}) }}
And the following method in your parent component:
public function newVariant(): Variant
{
return Variant::createWithOptions($this->product->getOptions());
}
If you update your product - and for instance, its options. You will have a wrong VariantForm because it will not be re-rendered!
There's a documented solution for this kind of things. Symfony UX suggest to add the option updateFromParent: true
on your LiveProp
.
That way the property updated on the parent will trigger an update of the child.
Yes, you can update a prop automatically... But you may have notice it or have the same issue: the product is not passed to the VariantForm. Therefore you cannot update the child component.
This is not entirely true, you can trigger an event and listen in the child component, the component will be automatically refreshed in this case. But we are going to use another approach more in the veine of what should be done in the Symfony UX world.
Yes, it seems useless to inject the product in the VariantForm, but the options comes from the product, so you don't really have a choice here!
A part of the solution is to add a LiveProp
that will be updated:
class VariantForm
{
#[LiveProp()]
public Variant $variant;
#[LiveProp(updateFromParent: true)]
public Product $product;
}
But this is not enough! As I said, you need the product, and you need to use it to move the data of your component.
It may not feel natural but you need to update the data of the rest of your component depending on this update. To achieve this you should use a special parameter of LiveProp
: onUpdated
.
#[LiveProp(updateFromParent: true, onUpdated: 'onProductUpdate')]
public Product $product;
public function onProductUpdate($previousData): void
{
$this->product; // This variable is the new (updated) one!
}
For some of you, the journey may end. But not for everybody!
This is kindof a very weird behavior here. If you are using a form, when the component (and the prop) is updated, the submit of the form is triggered... Even before the call onUpdated
.
And the submit of the form may trigger a validation error blocking the render to continue behind the scene. This may actually be an issue of Symfony UX. But for now it work this way.
To avoid this auto-submit to happen, it exist a non-documented (yet) option: shouldAutoSubmitForm
. You can set it on your component at anytime, such as construct time for example .
public function __construct()
{
// Avoid submitting sometimes empty data
$this->shouldAutoSubmitForm = false;
}
That should be all! Your component now updates correctly if you followed all those steps.