symfony / ux

Symfony UX initiative: a JavaScript ecosystem for Symfony
https://ux.symfony.com/
MIT License
820 stars 297 forks source link

[RFC] Alternate syntax for ComponentWithFormTrait #932

Open weaverryan opened 1 year ago

weaverryan commented 1 year ago

The current ComponentWithFormTrait setup looks like this:


#[AsLiveComponent]
class PostForm extends AbstractController
{
    use DefaultActionTrait;
    use ComponentWithFormTrait;

    #[LiveProp(fieldName: 'data')]
    public Post $post = null;

    protected function instantiateForm(): FormInterface
    {
        return $this->createForm(PostType::class, $this->post);
    }
}

The purpose of the $post property is confusing. People often think that the component is updating the $post property directly. In reality, the data/model sent to the component is used to "submit the form"... and that, of course, will then update the Post object. But the fields on $post are not directly writable. The entire property is almost just an internal detail so that your form is bound to the underlying data you want - e.g. if you are editing Post with id=2, we want you to always have that same id=2 entity bound to your form.

Because of this sometimes people make the $post property writable or expect it to be updated earlier than it is - e.g. creating a LiveAction and referencing $this->post to try to get the current data (before calling $this->submitForm().

There are 2 theoretical solutions:

A) Make the $post property "stay updated" with whatever the state was on the last form submit. This, unfortunately, isn't possible unless we kept both a copy of the current form data AND the "previous" form data and we submitted the form twice on each ajax call... which seems like asking for trouble (submit once with the old data so that the Post object looks like it did on the previous request, then re-create a 2nd form with that updated Post object, then submit it a 2nd time with the new data).

B) Hide the $post property so that it fades into the background as an implementation detail. Then work on other DX improvements as needed once this confusing details is hidden. I think this is the only viable solution. Proposal is a new static method to "describe" your form's class and its bound data class:

class PostForm
{
    use DefaultActionTrait;
    use ComponentWithFormTrait;

    public static function configureForm(ComponentFormConfig $config)
    {
        $config->formType(PostType::class)
            ->dataClass(Post::class)
            // optional, if you don’t pass initialFormData and want custom logic to create the initial data
            //  ->defaultData(fn => new EntityClass())
            ;
    }
}

Using the form would look like this (you could still optionally pass a form variable if you first create your form in a controller):

{{ component('PostForm', {
    initialFormData: post,
}) }}

The initialFormData would become a special prop name you always pass in (unless this is a "new" form and you don't need to bind any data). There WOULD be a public $initialFormData LiveProp on the ComponentWithFormTrait (which serves the same purpose as the $post property above), but would be less noticeable.

Cheers!

carsonbot commented 5 months ago

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

carsonbot commented 4 months ago

Hello? This issue is about to be closed if nobody replies.

carsonbot commented 4 months ago

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!