Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TwigComponent][LiveComponent] Fix DataModelPropsSubscriber for embedded components #1093

Merged
merged 1 commit into from Sep 5, 2023

Conversation

sneakyvv
Copy link
Contributor

@sneakyvv sneakyvv commented Sep 4, 2023

Q A
Bug fix? yes
New feature? no
Tickets
License MIT

Reproduce

{# components/A.html.twig #}

<B>
  {# content doesn't matter, as long as it's an embedded component #}
</B>
{# components/B.html.twig #}

<C data-model="X"/>

(given a component B with a prop X)

With this setup the DataModelPropsSubscriber::preMount() would result in an error "Can't get a way to read the property "X" in class "A"".

An even simpler setup

{# anyTemplate.html.twig #}

<A>
{# A is already embedded #}
</A>

Will result in "You can only pass "data-model" when rendering a component when you're rendering inside of a parent component."
Whether the data-model attribute is deep inside template B or already in template A doesn't matter. The thing is that during rendering of A, which is now embedded, there's no component on the stack.

Problem

Self-closing component are rendered via ComponentRenderer::createAndRender().
Embedded components are rendered via ComponentRenderer::embeddedContext() + Template::display().

Both push the component being rendered on the ComponentStack, and pop it again at the end of the ComponentRender functions. During the rendering of C, B is already been popped from the stack, and therefore the DataModelPropsSubscriber doesn't have a way to find the parent component context. And it incorrectly uses component A, which is still on the component stack (while it should be linked to B).

Solution

The rendering of an embedded component is only really done after the Template::display() execution. Therefore the component can only be removed when BOTH are done.

@sneakyvv
Copy link
Contributor Author

sneakyvv commented Sep 5, 2023

Triggered a test re-run via force-push.
There were a lot of checks failing with '_Can't use 'tar -xzf' extract archive file: /home/runner/work/_actions/temp_7abfb70c-3c2b-4efa-88de-68305c3e9cfc/de1fe6ea-d403-4c00-86a3-50ae693470fa.tar.gz. return code: 2.'

Copy link
Member

@weaverryan weaverryan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

COMPLEX - but it makes sense, and an excellent test to cover it.

private function getCurrentLiveComponent(ComponentStack $componentStack): ?MountedComponent
{
$iterator = $componentStack->getIterator();
while (($current = $iterator->current()) && !$this->isLiveComponent($current->getComponent()::class)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it now implements IteratorAggregate, can we just foreach ($componentStack as $component)?

Also, can you explain this a little? It's possible that the current component is some other, non-live component? And so we need to look further "up"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. If you have for example a, I don't know, Card component as a parent of an Input component, all inside of a Product component, and the input is supposed to control a property of the Product. In HTML this will already be ok, since the product-HTML will have the data-live properties, and the card won't. So, here in PHP, we also need to link it to that Live Component.

I adjusted the comment that was already there.

@sneakyvv sneakyvv force-pushed the fix-embedded-component-stack branch 2 times, most recently from 58143b3 to 9815f9f Compare September 5, 2023 18:42
@weaverryan
Copy link
Member

Thank you @sneakyvv!

@weaverryan weaverryan merged commit 19feb79 into symfony:2.x Sep 5, 2023
9 of 10 checks passed
@sneakyvv sneakyvv deleted the fix-embedded-component-stack branch September 5, 2023 18:44
weaverryan added a commit that referenced this pull request Sep 7, 2023
…hildComponentRenderSubscriber (sneakyvv)

This PR was merged into the 2.x branch.

Discussion
----------

[LiveComponent] Only consider Live components in InterceptChildComponentRenderSubscriber

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| Tickets       |
| License       | MIT

This PR is related to the change of #1093.

### Problem
The component stack now also contains embedded Components until they are fully rendered, which is good. But, that also means that with this setup

```
{# someTemplate #}

<twig:aLiveComponent>
    <twig:aTwigComponent>
        <twig:anotherLiveComponent>
            {# ... #}
        </twig:anotherLiveComponent>
    </twig:aTwigComponent>
</twig:aLiveComponent>
```
when re-rendering the `anotherLiveComponent` the `InterceptChildComponentRenderSubscriber` would look at the parent component's `childrenFingerprints` to determine if it can short-circuit the rendering process for `anotherLiveComponent` and return an empty html element causing the FE to not do a callback to re-render it. However since that parent is a non-live component it will never short-circuit.

### Solution

Only take live components into account.

Commits
-------

953721e [LiveComponent] Only consider Live components in InterceptChildComponentRenderSubscriber
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants