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

[LiveComponent] Custom form errors not displayed when invoking live action #1123

Open
IvoPereira opened this issue Sep 18, 2023 · 11 comments
Open

Comments

@IvoPereira
Copy link

Hi there.

I believe I encountered an issue with the Symfony UX's ComponentWithFormTrait where form errors are not displayed when a live action is invoked from a component template.

To give a brief context: I have some business logic that throws some very specific Domain Errors that I would need to map and display to the user.

I'm using the ComponentWithFormTrait to handle forms in my live component.
When invoking a live action, I'm trying to add both global form errors and specific field errors. However, despite adding these errors, they are not displayed in the form.

Reproduction Steps:

  • Create a form using ComponentWithFormTrait.
  • In the live action method, add a global form error and a field-specific error.
  • Invoke the live action from the component template.
  • Observe that the errors are not displayed in the form.

Minimal Reproducible Code:
Component:

use ComponentWithFormTrait;

#[LiveAction]
public function save()
{
    // Attempting to add a global form error
    $this->getForm()->addError(new \Symfony\Component\Form\FormError('General error'));
    
    // Attempting to add a specific field error
    $this->getForm()->get('description')->addError(new \Symfony\Component\Form\FormError('Form field error'));
}

Template:

<div {{ attributes }}>
    {{ form_start(form, {
        attr: {
            'data-action': 'live#action',
            'data-action-name': 'prevent|save'
        }
    }) }}
    {{ form_errors(form) }}
    {{ form_row(form.description) }}

    <button type="submit">Save</button>
    {{ form_end(form) }}
</div>

If it makes any difference, my form is using a data-transfer object for the form data. I have noticed that with a regular Symfony action+Twig template this does work.

I suspect that somehow the form instance might not be the same getting rendered?

Is this a known issue, or is there a recommended way to force the form errors to display in this scenario?

@IvoPereira IvoPereira changed the title Form errors not displayed when invoking live action Custom form errors not displayed when invoking live action Sep 18, 2023
@weaverryan
Copy link
Member

Hey Ivo!

Real quick, does it help if you submit the form first in your LiveAction?

use ComponentWithFormTrait;

#[LiveAction]
public function save()
{
    $this->submitForm(0;

    // Attempting to add a global form error
    $this->getForm()->addError(new \Symfony\Component\Form\FormError('General error'));
    
    // Attempting to add a specific field error
    $this->getForm()->get('description')->addError(new \Symfony\Component\Form\FormError('Form field error'));
}

From the code I see above, you were not calling this. When you don't submit a form in your LiveAction, it is submitted for your AFTER the LiveAction. So it's possible that you're adding the error... then it's immediately cleared out when the form submits a moment later.

Cheers!

@IvoPereira
Copy link
Author

Hi Ryan!

It doesn't, unfortunately.

I also tried that before, now I had totally forgotten to include it in the reproducible example.

Let me know if there is anything else I can test.

Cheers

@weaverryan
Copy link
Member

The problem might be that $this->submitForm() throws an exception if the form is invalid. This is so that, if you write code below it to handle success, it's not called. I think this might be working against you. Try:

try {
    $this->submitForm();
} (catch UnprocessableEntityHttpException $e) {
    // add your custom error here

    throw $e;
}

@IvoPereira
Copy link
Author

IvoPereira commented Sep 21, 2023

Thanks for the quick reply.

Let me provide a bit more context (I didn't provide it initially, as I believed the issue was not related).

In my forms, after submitForm() and being sure the form is valid, I usually dispatch a handler/call a service to validate/persist my domain data.

Let's say I am building a signup form. I have in my service a validation that checks for instance whether the given email is unique. If it isn't, it throws an EmailAlreadyInUseException.

Tldr; while the majority of invalid fields can be caught using the form validations, I need to catch service Exceptions and map them to form fields as FormErrors as well.

Simplified example:

use ComponentWithFormTrait;

#[LiveAction]
public function save()
{
    $this->submitForm();
    // form should be valid at this stage

    try {
        $this->userService->create($this->dto); // let's say we initially created the form with a dto
    } catch (EmailAlreadyInUseException) {
        // Attempting to add a global form error
        $this->getForm()->addError(new \Symfony\Component\Form\FormError('General error: Email already in use'));
    
        // Attempting to add a specific field error
        $this->getForm()->get('email')->addError(new \Symfony\Component\Form\FormError('Field error: Email already in use'));
    }
}

@IvoPereira IvoPereira changed the title Custom form errors not displayed when invoking live action [LiveComponent] Custom form errors not displayed when invoking live action Sep 21, 2023
@carsonbot
Copy link

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

@carsonbot
Copy link

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

@carsonbot
Copy link

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!

@dpinheiro
Copy link

This issue is still relevant, I'm facing the same problem

@carsonbot carsonbot removed the Stalled label Jun 5, 2024
@IvoPereira
Copy link
Author

Unfortunately I am still facing this as well.

Did you had a chance to look further into the context I have added last time @weaverryan?

@smnandre
Copy link
Member

smnandre commented Jun 5, 2024

Did you guys tried to throw a UnprocessableEntityExcetion there ?

@dpinheiro
Copy link

Did you guys tried to throw a UnprocessableEntityExcetion there ?

Yes, I'm throwing an UnprocessableEntityExcetion. I believe I have found the problem and a workaround for it.

When calling some live action method, the first call is to submitFormOnRender, and if the form was not yet submitted form then it's submitted, it gets validated, and then the formView is filled. Later when a custom exception is thrown, we associate the error to the form, and throw an UnprocessableEntityExcetion so then it's handled by the method onKernelException of the class LiveComponentSubscriber. There it will try to create a response using the ComponentRenderer, that will try to obtain the component variables and then render the associated twig template with those variables. The method exposedVariables is obtaining the exposed formView (#[ExposeInTemplate(name: 'form', getter: 'getFormView')]), the problem is that this formView is cached, so it only contains the initial status, and not the new error that was added later.

A workaround that worked for me was to reset the formView to force it to be calculated again.

Taking the example from @IvoPereira

use ComponentWithFormTrait;

#[LiveAction]
public function save()
{
    $this->submitForm();
    // form should be valid at this stage

    try {
        $this->userService->create($this->dto); // let's say we initially created the form with a dto
    } catch (EmailAlreadyInUseException) {        
        // Attempting to add a global form error
        $this->getForm()->addError(new \Symfony\Component\Form\FormError('General error: Email already in use'));
    
        // Attempting to add a specific field error
        $this->getForm()->get('email')->addError(new \Symfony\Component\Form\FormError('Field error: Email already in use'));
        
        $this->formView = null; // this will clean the formView status so it could be rendered with the new errors that were added
    }
}

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

No branches or pull requests

5 participants