Skip to content

[LiveComponent] Batch request to save function, throws error: The submitForm() method is being called, but the FormView has already been built #1509

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

Closed
jpvdw86 opened this issue Feb 16, 2024 · 8 comments · Fixed by #2553 · May be fixed by #1683
Labels

Comments

@jpvdw86
Copy link

jpvdw86 commented Feb 16, 2024

A normal request works fine, however, if you edit multiple fields at the same time, the event is converted into a _batch request. When this happens, I get the following error 99% of the time: The submitForm() method is being called, but the FormView has already been built.

Scherm­afbeelding 2024-02-16 om 14 41 49

Example batch request body
{"props":{"inputModel":{"date_filter":{"min_date":"2024-02-16T00:00:00+01:00","max_date":null},"import":false,"invoice":{"line_items":[],"children":true,"groups":[{"line_items":[],"group_code":null,"description":"","children":true,"groups":[{"line_items":[{"vatable":true,"description":"Voertuig","financial_party":null,"amount_ex_vat":1200,"vat_amount":252,"totals":{"amountExVat":1200,"vatAmount":252,"amountInclVat":1452,"vatRate":21},"calculated_vat_rate":21},{"vatable":true,"description":"Rest BPM","financial_party":null,"amount_ex_vat":0,"vat_amount":0,"totals":{"amountExVat":0,"vatAmount":0,"amountInclVat":0,"vatRate":0},"calculated_vat_rate":0}],"group_code":"purchase","description":"Inkoop","children":false,"groups":[],"totals":{"totalAmountExVat":1200,"totalVatableAmountExVat":1200,"totalVatAmount":252,"totalAmountInclVat":1452,"groupedVat":{"21":{"vatAmount":252,"amountExVat":1200}}}},{"line_items":[{"vatable":true,"description":"Transportkosten","financial_party":null,"amount_ex_vat":0,"vat_amount":0,"totals":{"amountExVat":0,"vatAmount":0,"amountInclVat":0,"vatRate":0},"calculated_vat_rate":0}],"group_code":"costs","description":"Kosten","children":false,"groups":[],"totals":{"totalAmountExVat":0,"totalVatableAmountExVat":0,"totalVatAmount":0,"totalAmountInclVat":0,"groupedVat":[]}},{"line_items":[{"vatable":true,"description":"Optische herstelkosten","financial_party":null,"amount_ex_vat":0,"vat_amount":0,"totals":{"amountExVat":0,"vatAmount":0,"amountInclVat":0,"vatRate":0},"calculated_vat_rate":0},{"vatable":true,"description":"Technische herstelkosten","financial_party":null,"amount_ex_vat":0,"vat_amount":0,"totals":{"amountExVat":0,"vatAmount":0,"amountInclVat":0,"vatRate":0},"calculated_vat_rate":0},{"vatable":true,"description":"Reconditionering fee","financial_party":null,"amount_ex_vat":302,"vat_amount":63.42,"totals":{"amountExVat":302,"vatAmount":63.42,"amountInclVat":365.42,"vatRate":21},"calculated_vat_rate":21}],"group_code":"expected_costs","description":"Verwachte kosten","children":false,"groups":[],"totals":{"totalAmountExVat":302,"totalVatableAmountExVat":302,"totalVatAmount":63.42,"totalAmountInclVat":365.42,"groupedVat":{"21":{"vatAmount":63.42,"amountExVat":302}}}}],"totals":{"totalAmountExVat":1502,"totalVatableAmountExVat":1502,"totalVatAmount":315.42,"totalAmountInclVat":1817.42,"groupedVat":{"21":{"vatAmount":315.42,"amountExVat":1502}}}}],"totals":{"totalAmountExVat":1502,"totalVatableAmountExVat":1502,"totalVatAmount":315.42,"totalAmountInclVat":1817.42,"groupedVat":{"21":{"vatAmount":315.42,"amountExVat":1502}}}},"top_down_protocol":"App\\Domain\\Inflow\\Settings\\Resolver\\Model\\TopDownPurchaseProtocol"},"vehicle":255,"source":"Aanmaken dossier","personalValuationModel":{"value":4353,"etr":2,"valuationId":4232},"readOnly":false,"protocol":"topdown_purchase","formName":"valuation_form","valuation_form":{"value":"4.353","etr":"2","delete":null,"_token":"90c999283.CNUvXgcEh9QobFpu-5FLj-RGoLObCobHhB_3m1OhB9E.P6J3D2Qpz5VFFQJbjtA5_bUfycmvZ7W-wXuzwz6UT45q5EBpVE7kkUscNQ"},"isValidated":true,"validatedFields":[],"@attributes":{"data-live-id":"live-614878759-0"},"@checksum":"zePMOuTPbTkvAMsm//7BfefQOs1i2hHzBzfWqCus2ro="},"updated":{"valuation_form.etr":"2","validatedFields":["valuation_form.etr"]},"actions":[{"name":"save","args":{}},{"name":"save","args":{}}]}

Normal save action, that works fine:
{"props":{"inputModel":{"date_filter":{"min_date":"2024-02-16T00:00:00+01:00","max_date":null},"import":false,"invoice":{"line_items":[],"children":true,"groups":[{"line_items":[],"group_code":null,"description":"","children":true,"groups":[{"line_items":[{"vatable":true,"description":"Voertuig","financial_party":null,"amount_ex_vat":1200,"vat_amount":252,"totals":{"amountExVat":1200,"vatAmount":252,"amountInclVat":1452,"vatRate":21},"calculated_vat_rate":21},{"vatable":true,"description":"Rest BPM","financial_party":null,"amount_ex_vat":0,"vat_amount":0,"totals":{"amountExVat":0,"vatAmount":0,"amountInclVat":0,"vatRate":0},"calculated_vat_rate":0}],"group_code":"purchase","description":"Inkoop","children":false,"groups":[],"totals":{"totalAmountExVat":1200,"totalVatableAmountExVat":1200,"totalVatAmount":252,"totalAmountInclVat":1452,"groupedVat":{"21":{"vatAmount":252,"amountExVat":1200}}}},{"line_items":[{"vatable":true,"description":"Transportkosten","financial_party":null,"amount_ex_vat":0,"vat_amount":0,"totals":{"amountExVat":0,"vatAmount":0,"amountInclVat":0,"vatRate":0},"calculated_vat_rate":0}],"group_code":"costs","description":"Kosten","children":false,"groups":[],"totals":{"totalAmountExVat":0,"totalVatableAmountExVat":0,"totalVatAmount":0,"totalAmountInclVat":0,"groupedVat":[]}},{"line_items":[{"vatable":true,"description":"Optische herstelkosten","financial_party":null,"amount_ex_vat":0,"vat_amount":0,"totals":{"amountExVat":0,"vatAmount":0,"amountInclVat":0,"vatRate":0},"calculated_vat_rate":0},{"vatable":true,"description":"Technische herstelkosten","financial_party":null,"amount_ex_vat":0,"vat_amount":0,"totals":{"amountExVat":0,"vatAmount":0,"amountInclVat":0,"vatRate":0},"calculated_vat_rate":0},{"vatable":true,"description":"Reconditionering fee","financial_party":null,"amount_ex_vat":302,"vat_amount":63.42,"totals":{"amountExVat":302,"vatAmount":63.42,"amountInclVat":365.42,"vatRate":21},"calculated_vat_rate":21}],"group_code":"expected_costs","description":"Verwachte kosten","children":false,"groups":[],"totals":{"totalAmountExVat":302,"totalVatableAmountExVat":302,"totalVatAmount":63.42,"totalAmountInclVat":365.42,"groupedVat":{"21":{"vatAmount":63.42,"amountExVat":302}}}}],"totals":{"totalAmountExVat":1502,"totalVatableAmountExVat":1502,"totalVatAmount":315.42,"totalAmountInclVat":1817.42,"groupedVat":{"21":{"vatAmount":315.42,"amountExVat":1502}}}}],"totals":{"totalAmountExVat":1502,"totalVatableAmountExVat":1502,"totalVatAmount":315.42,"totalAmountInclVat":1817.42,"groupedVat":{"21":{"vatAmount":315.42,"amountExVat":1502}}}},"top_down_protocol":"App\\Domain\\Inflow\\Settings\\Resolver\\Model\\TopDownPurchaseProtocol"},"vehicle":255,"source":"Aanmaken dossier","personalValuationModel":{"value":4353,"etr":4,"valuationId":4232},"readOnly":false,"protocol":"topdown_purchase","formName":"valuation_form","valuation_form":{"value":"4.353","etr":"4","delete":null,"_token":"a83e3401b4758c18f7529d5d5f4cc.3E69vLJqTsTTkD2XPa0QP6oOMOtFtpaeCeEujEJI_Jg.6znl7dFHBoW-6WWiSOxiTftXWZFx26XnTIVq1C99tMe-f9KL4SAtgbDgUg"},"isValidated":true,"validatedFields":[],"@attributes":{"data-live-id":"live-614878759-0"},"@checksum":"bHPdAhbWdZPi+A/x05NDs1CHCD7w0m29PAbJBVgQfH0="},"updated":{"valuation_form.etr":"2","validatedFields":["valuation_form.etr"]},"args":{}}

@WebMamba
Copy link
Contributor

Hey @jpvdw86 are you up to sharing a little reproducer? It can be easier to help you! 😁

@borisceranic
Copy link

borisceranic commented Feb 28, 2024

Hi @WebMamba,

The same thing is happening to me, too. While I can not help by sharing a reproducer, as @jpvdw86 said, I also have some anecdotal evidence that says this happens while user is doing multiple unrelated actions in the form quickly enough for JS to decide to batch multiple operations together.

The issue is triggered by the following part of the props JSON:

{"props":{
  "actions":[
    {"name":"save","args":{}},
    {"name":"save","args":{}}
  ]
}

So, whatever leads _batch to capturing two different events that trigger the same action, is a cause for this, because apparently, the two actions are triggered within the same component's context, thus the underlying form complains in the way reported by @jpvdw86. In his case, the action is save(), in my case, the action called twice is submit(). Regardless, the effect is the same - it leads to an exception.

Hopefully this gives you some clues as to where the issue might lie?

@rwkt
Copy link

rwkt commented Mar 26, 2024

I'm running into the same issue. Have also tried debounce but doing things fast enough leads to _batch and the error.

#[LiveAction]
public function save(EntityManagerInterface $entityManager): void
{
     $this->submitForm();
}
{{ form_widget(form.note, {'attr': {'data-action': 'live#action:prevent', 'data-live-action-param': 'on(change)|save'}}) }}

Error

The submitForm() method is being called, but the FormView has already been built. Are you calling $this->getForm() - which creates the FormView - before submitting the form?

@EOL-Fred
Copy link

EOL-Fred commented Mar 29, 2024

I have the same problem.
In my case, I have an HTML grid with several forms. In my grid, I can select the fields that I edit. One of the fields is an EntityType, rendered as a select, when it saves, here is the request :
{ "props": { "initialFormData": 13, "company": 13, "company.name": "DUMMY COMPANY", "company.legalName": "DUMMY COMPANY", "editable": true, "columns": [ "id", "name", "address", "registrationNumber", "legalForm" ], "readOnlyColumns": [ "id", "address" ], "personColumns": [ "id", "firstName", "lastName", "phone", "mobile" ], "readOnlyPersonColumns": [ "id" ], "isValid": true, "formName": "company_row", "company_row": { "legalName": "DUMMY COMPANY", "name": "DUMMY COMPANY", "brandName": "", "headQuarter": null, "registrationNumber": "", "registeredAt": "", "vatKey": "", "website": "", "email": "[email protected]", "shareCapital": "", "isEol": null, "phone": "", "activityCode": "", "legalForm": "24", "_token": "3b08b7120c40c35dbd931.1CnyNbe98FLb6mYYA4VCn-n1ZNoQnly0azGgPkfJywI.i227cf_VvRrjxzxVe_B1p7OYAeJG8y7XLgHkeXGf_VaRQbYEg86IIZSLMw" }, "isValidated": true, "validatedFields": [], "@attributes": { "id": "live-1209671343-company_group_39867153_13", "key": "company_group_39867153_13" }, "@checksum": "QS52v1m7uFfOnkJWovQugsnN5IKXkdFWUsJZ5x2AbL4=" }, "updated": { "company_row.legalForm": "57", "validatedFields": [ "company_row.legalForm" ] }, "args": {} }
And it works.

When I set the option autocomplete to true (with UX Autocomplete), when I save it triggers a _batch request (no need to edit multiple fields at the same time) :
{ "props": { "initialFormData": 13, "company": 13, "company.name": "DUMMY COMPANY", "company.legalName": "DUMMY COMPANY", "editable": true, "columns": [ "id", "name", "address", "registrationNumber", "legalForm" ], "readOnlyColumns": [ "id", "address" ], "personColumns": [ "id", "firstName", "lastName", "phone", "mobile" ], "readOnlyPersonColumns": [ "id" ], "isValid": true, "formName": "company_row", "company_row": { "legalName": "DUMMY COMPANY", "name": "DUMMY COMPANY", "brandName": "", "headQuarter": null, "registrationNumber": "", "registeredAt": "", "vatKey": "", "website": "", "email": "[email protected]", "shareCapital": "", "isEol": null, "phone": "", "activityCode": "", "legalForm": "57", "_token": "0cd266cd3021abd2a.ENVaEp2ESvr1xw0_rLnsE1-wsE7kB8UQkhNMR1u3PWk.T5ETVtXsB7LN6ldy1MzbKwXd1Xayardz1yMIAG3hCz1VvR4jqfcyibqmWA" }, "isValidated": false, "validatedFields": [], "@attributes": { "id": "live-1209671343-company_group_430929945_13", "key": "company_group_430929945_13" }, "@checksum": "UkbsuEMubWWNvTrlqFFioIQ7OT2+uOhFNZzTX69ZoJQ=" }, "updated": { "company_row.legalForm": "", "validatedFields": [ "company_row.legalForm" ] }, "actions": [ { "name": "save", "args": {} }, { "name": "save", "args": {} } ] }
And it triggers the error

@jpvdw86
Copy link
Author

jpvdw86 commented Apr 4, 2024

@WebMamba @EOL-Fred

This issue is caused by the following code:

//Symfony\UX\LiveComponent\Controller\BatchController.php

foreach ($actions as $action) {
    $name = $action['name'] ?? throw new BadRequestHttpException('Invalid JSON');

    $subRequest = $request->duplicate(attributes: [
        '_controller' => [$serviceId, $name],
        '_component_action_args' => $action['args'] ?? [],
        '_mounted_component' => $_mounted_component,
        '_live_component' => $serviceId,
    ]);

    $response = $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false);

    if ($response->isRedirection()) {
        return $response;
    }
}

In this scenario, the function is invoked within the same HTTP request (kernel instance). As a consequence of the first request, formValues has become non-nullable. Subsequently, the second request checks for this and triggers the error.

The Symfony\UX\LiveComponent\ComponentWithFormTrait does not support batch request behavior by default.

I've already attempted to use $this->resetForm() function. However, this did not resolve the error.

The resetForm function reinitializes this->formView (using $this->getFormView()).
//Symfony\UX\LiveComponent\ComponentWithFormTrait

private function resetForm(): void
    {
        // prevent the system from trying to submit this reset form
        $this->shouldAutoSubmitForm = false;
        $this->form = null;
        $this->formView = null;
        $this->formValues = $this->extractFormValues($this->getFormView());
    }

I haven't investigated further, and perhaps the fix is simple by adjusting the order in the resetForm function to the following:

private function resetForm(): void
{
    // prevent the system from trying to submit this reset form
    $this->formValues = $this->extractFormValues($this->getFormView());
    $this->shouldAutoSubmitForm = false;
    $this->form = null;
    $this->formView = null;
}

@weaverryan, perhaps you can provide a simple answer to this. If it's that straightforward, I'll submit a pull request for it.

For now, I'm using the following code to fix this issue:

if (null !== $this->formView) {
    $this->shouldAutoSubmitForm = false;
    $this->form = null;
    $this->formView = null;
}
$this->submitForm();

@kkevindev
Copy link

@WebMamba when will this be resolved?

@smnandre
Copy link
Member

smnandre commented Feb 6, 2025

I just tried something derived from your suggestion @jpvdw86 (thank you very much)

    /**
     * Reset the form to its initial state, so it can be used again.
     */
-   private function resetForm(): void
+   private function resetForm(?bool $soft = false): void
    {
        // prevent the system from trying to submit this reset form
        $this->shouldAutoSubmitForm = false;
        $this->form = null;
        $this->formView = null;
+        if (true !== $soft) {
            $this->formValues = $this->extractFormValues($this->getFormView());
+        }
    }

    private function submitForm(bool $validateAll = true): void
    {
        if (null !== $this->formView) {
+           $this->resetForm(true);
-           throw new \LogicException('The submitForm() method is being called, but the FormView has already been built. Are you calling $this->getForm() - which creates the FormView - before submitting the form?');
        }

        $form = $this->getForm();
        $form->submit($this->formValues);

What we lose

  • an exception that is more a DX warning than anything else, as there is no real use case that could/should trigger this exception for someone following the documentation.

What we keep

  • existing behaviour for LiveAction(s) outside ComponentWithFormTrait (this excludes executing only some of the requests)
  • existing behaviour / way of dealing with formValues

What we win

  • a fix against this bug (happening on live websites)

What do you all think? Seems OK ? Could some of you maybe just test if this does fix your problems ?

@smnandre
Copy link
Member

smnandre commented Feb 6, 2025

I've opened the PR, if you guys can check it that'd be nice!

@kkevindev @jpvdw86 @EOL-Fred

@carsonbot carsonbot added the Status: Needs Review Needs to be reviewed label Feb 6, 2025
smnandre added a commit that referenced this issue Feb 7, 2025
…atch actions (smnandre)

This PR was merged into the 2.x branch.

Discussion
----------

[LiveComponent] Fix ComponentWithFormTrait not working in batch actions

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| Issues        | Fix #1509
| License       | MIT

Fix #1509

Thank you `@jpvdw86` for your work on this! 👏

Some parts of your PR would have introduced changes affecting all LiveComponent instances, including those not using ComponentWithFormTrait. To keep the impact as minimal as possible, I’ve opted for a more targeted fix.

But _you_ did all the hard work here—much appreciated! 🚀

Commits
-------

466c4eb [LiveComponent] Fix ComponentWithFormTrait not working in batch actions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
8 participants