ux icon indicating copy to clipboard operation
ux copied to clipboard

[LiveComponent] save model values on "onUpdated" param

Open bastien70 opened this issue 2 years ago • 9 comments

Hello, in my LiveComponent, I've this :

#[LiveProp(writable: true, onUpdated: 'onQueryUpdated')]
    public ?string $query = null;

    #[LiveProp]
    public array $items = [];

    public function onQueryUpdated($previousValue): void
    {
        $this->loadItems();
        $this->dispatchBrowserEvent('items:loaded');
    }

The goal is to simply trigger an event on my stimulus controller as soon as the "query" model is updated.

The "loadItems" method will do all the processing and assign to the "items" model.

The problem is that just before the dispatchBrowserEvent, if I dump the content of the "items" model, I have the new content, but not from my stimulus controller, which keeps the old value

window.addEventListener('items:loaded', (event) => {
            loadChart(this.component);
        });
        
        function loadChart(component)
        {
            var jsonData = component.getData('items');
            console.log(jsonData); // still the previous value
      }

Is this normal behavior? Is there a method to run from the component to tell it to "flush" the new model values ​​so that from the stimulus controller, I can retrieve it?

bastien70 avatar Nov 20 '23 16:11 bastien70

Could you create a little small reproducer, it's easier to understand what you are doing / expecting.

But right now i'd say the component has not been re-rendered, so this.component still contain original items ?

smnandre avatar Nov 20 '23 22:11 smnandre

This is a reproducer : https://github.com/bastien70/live-component-onUpdated-reproducer

Description

In this simple and unnecessary example, we will display a date in either French or English format, depending on the checkbox.

The update of the component's "date" model will be carried out at postMount but also when the "displayAsFrench" model is updated.

Installation

composer install yarn install --force yarn run build symfony serve

Files

Component :

#[AsLiveComponent('date', template: 'components/date.html.twig')]
final class DateComponent
{
    use DefaultActionTrait;
    use ComponentToolsTrait;

    #[LiveProp(writable: true, onUpdated: 'onDisplayAsFrenchUpdated')]
    public bool $displayAsFrench = false;

    #[LiveProp]
    public ?string $date = null;

    #[PostMount]
    public function postMount(): array
    {
        $this->reloadDate();

        return [];
    }

    public function onDisplayAsFrenchUpdated($previousValue): void
    {
        // $this->query already contains a new value
        // and its previous value is passed as an argument
        $this->reloadDate();
        dump($this->date);
        $this->dispatchBrowserEvent('refresh:date');
    }

    public function reloadDate(): void
    {
        $date = new \DateTime();

        if($this->displayAsFrench)
        {
            $this->date = $date->format('d/m/Y H:i:s');
        } else {
            $this->date = $date->format('Y-m-d H:i:s');
        }
    }
}

Stimulus controller :

// assets/controllers/some-custom-controller.js
// ...
import { Controller } from '@hotwired/stimulus';
import { getComponent } from '@symfony/ux-live-component';

export default class extends Controller {

    async initialize() {
        this.component = await getComponent(this.element);

        // Get live component
        updateValue(this.component);

        this.component.on('render:started', (component) => {
            // do something after the component re-renders
        });

        this.component.on('render:finished', (component) => {
            // loadChart(component);
        });

        window.addEventListener('refresh:date', (event) => {
            updateValue(this.component);
        });

        function updateValue(component) {
            let date = component.getData('date')

            console.log(date);
            document.querySelector('#dateValue').innerHTML = date;
        }
    }
}

template :

<div{{ attributes.add(stimulus_controller('display-date')) }}>
    <div class="form-check form-switch">
        <input class="form-check-input" type="checkbox" role="switch" id="displayAsFrench" data-model="displayAsFrench">
        <label class="form-check-label" for="displayAsFrench">Display as French</label>
    </div>

    <b>Value setted from controller :</b>
    <div id="dateValue"></div>
</div>

Usage

Just go to https://127.0.0.1:8000/ The date is displayed in English format. Click on the “Display as French” checkbox. The onUpdated is triggered, and the code of the onDisplayAsFrenchUpdated method is launched. The date is then updated. The value is dumped, you can find it in the profiler. A dispatchBrowserEvent is fired to update the contents of the HTML, based on the contents of "date", but it retains the old value. (check console)

Demo

This is what I have on the browser, and in the console initially. Good values

image image

Now, if I click on the checkbox, the onDisplayAsFrenchUpdated method is triggered. It renews the date, according the "displayAsFrench" new value, dump the value, and and dispatches the browser event which will refresh the HTML with the value.

In the profiler, I can see the (correct) new "date" value : image

But, in the browser, I've still the previous value :

image

If you're looking on the "console" tab, the "console.log(date)" displays the same old value :

image

In this function :

function updateValue(component) {
            let date = component.getData('date')

            console.log(date);
            document.querySelector('#dateValue').innerHTML = date;
        }

However, we have recovered the content of the "date" model from the component, but which does not seem to have been updated here whereas when we dump it from the component, we have the new value.

Hope this all helps you

bastien70 avatar Nov 21 '23 11:11 bastien70

Yes i understand.

You should start by making it work without the custom controller JS first (with an input text for example)

As you do not have any model in the HTML for the display date, you should follow those instructions if you want to update your model... or it'll keep the first data i think:

--> https://symfony.com/bundles/ux-live-component/current/index.html#working-with-the-component-in-javascript

smnandre avatar Nov 21 '23 13:11 smnandre

Hmm but you're right, as you want to do it .. it probably won't work.

Maybe instead of this event cascade you can use a simple action ?

smnandre avatar Nov 21 '23 14:11 smnandre

It was just a (completely stupid) example that makes no sense, to show the "problem". Yes I can do otherwise here, sure, but the goal is to show that there is a problem with the onUpdated.

So for cases where we really need onUpdated (me or someone else), the problem will be present :)

bastien70 avatar Nov 22 '23 07:11 bastien70

Well you are right. For now what you want to do will not work..

i'd still advise you to use LiveActions when you want to update your model (without persisting it) ... or to persist your data as you seem to require state storage

smnandre avatar Nov 22 '23 23:11 smnandre

Thank you for this issue. There has not been a lot of activity here for a while. Has this been resolved?

carsonbot avatar May 23 '24 12:05 carsonbot

Friendly reminder that this issue exists. If I don't hear anything I'll close this.

carsonbot avatar Jun 06 '24 12:06 carsonbot

Hey,

I didn't hear anything so I'm going to close it. Feel free to comment if this is still relevant, I can always reopen!

carsonbot avatar Jun 20 '24 12:06 carsonbot

Thank you for this issue. There has not been a lot of activity here for a while. Has this been resolved?

carsonbot avatar Dec 21 '24 12:12 carsonbot

Friendly ping? Should this still be open? I will close if I don't hear anything.

carsonbot avatar Jan 04 '25 12:01 carsonbot

Hey,

I didn't hear anything so I'm going to close it. Feel free to comment if this is still relevant, I can always reopen!

carsonbot avatar Jan 18 '25 12:01 carsonbot

Thank you for this issue. There has not been a lot of activity here for a while. Has this been resolved?

carsonbot avatar Jul 19 '25 12:07 carsonbot