ux icon indicating copy to clipboard operation
ux copied to clipboard

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

Open jpvdw86 opened this issue 1 year ago • 4 comments

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":{}}

jpvdw86 avatar Feb 16 '24 13:02 jpvdw86

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

WebMamba avatar Feb 20 '24 17:02 WebMamba

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?

borisceranic avatar Feb 28 '24 16:02 borisceranic

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?

rwkt avatar Mar 26 '24 13:03 rwkt

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

EOL-Fred avatar Mar 29 '24 10:03 EOL-Fred

@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();

jpvdw86 avatar Apr 04 '24 21:04 jpvdw86