[LiveComponents] Child component cannot read URL parameters if the parent component is defered
I have noticed that if I have nested components and the parent component is included with loading: 'defer', the child component can no longer read URL parameters. Here is my code:
Parent Component
<?php
namespace App\Twig\Components;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\DefaultActionTrait;
#[AsLiveComponent]
class ParentComponent
{
use DefaultActionTrait;
}
<div {{ attributes }}>
<h1>Parent Component</h1>
{{ component('ChildComponent', {
sort: 'name'
}) }}
</div>
Child Component
<?php
namespace App\Twig\Components;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;
#[AsLiveComponent]
class ChildComponent
{
use DefaultActionTrait;
#[LiveProp(writable: true, url: true)]
public string $sort = '';
}
<div {{ attributes }}>
<h1>Child Component</h1>
{{ dump(sort) }}
</div>
Main template:
{{ component('ParentComponent', { loading: 'defer' }) }}
If I now call up my page with ?sort=foo, the value “name” is dumped instead of “foo”. If I remove the { loading: 'defer' } from the parent component, the value “foo” is dumped. Is it possible in this scenario that the parent component remains defered and the child component can still read the URL parameters?
If the parent is deferrred, the rendering of its children is deferred too.
They are initialized with the value passed by their parent: name
So is it intentional that the children cannot use URL parameters in this case? Should then be mentioned in the documentation.
But you could solve it by checking in the QueryStringPropsExtractor at the beginning whether the current route is ux_live_component (this is the case for the children in my example). If it is, you could then extract the query string from the referer URL instead of from the current URL. Or is there something against this?
This should work for you:
<div {{ attributes }}>
<h1>Parent Component</h1>
{# Same id as in placeholder #}
<div id="child"></div>
</div>
{# Placeholder is render first and has access to the initial context #}
{% macro placeholder() %}
<div id="child" data-live-ignore="true">
<twig:ChildComponent sort="name" />
</div>
{% endmacro %}
Quick note — saying things like "you could solve it" and "you could then" made it a bit less encouraging to want to help. Just sharing for next time. :)
@smnandre Works, thank you!
Quick note — saying things like "you could solve it" and "you could then" made it a bit less encouraging to want to help.
Sorry, I didn't mean that you should solve it that way, but that I could solve it that way and that others with the same problem could solve it that way too (you = someone). And then I asked you if there was anything against it.
@smnandre I noticed a disadvantage of your solution (a little late). My parent component now has polling, so it reloads every 30 seconds: data-poll="delay(30000)|$render"
However, the child component is not updated with your solution. Do you have any other ideas on how I could solve this so that the child component is also reloaded?
This is my idea to fix the problem when a child component has URL parameters and the parent component is “deferred” or has “polling” enabled:
// In Symfony\UX\LiveComponent\Util\RequestPropsExtractor::extract
if ('ux_live_component' === $parameters['_route'] && $refererUrl = $request->headers->get('referer')) {
$refererRequest = Request::create($refererUrl);
$parameters = array_merge($parameters, $refererRequest->query->all());
}
If $parameters[‘_route’] has the value ux_live_component, this means that a component was loaded through "defer" or “polling”. In this case, the query parameters of the URL actually called up in the browser are missing in the request object. That is why I add the query parameters of the referer URL to $parameters. That solves the problem for me, but I don't know if there's any reason not to do it that way.