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] [Autocomplete] Autocomplete with custom data endpoint not working in a live component #1879

Open
momocode-de opened this issue Jun 2, 2024 · 4 comments

Comments

@momocode-de
Copy link

I have a problem when I use an autocomplete field, which uses a custom endpoint, together with a live component. I have simplified my case and added the files below. The problem is that when I select something in the autocomplete field, the message “The selected choice is invalid.” appears:

image

If I then click on “Save”, a message appears that I should select an element from the list, even though I have selected one:

image

Versions:

  • symfony/form: 7.0.7
  • symfony/ux-autocomplete: 2.17.0
  • symfony/ux-live-component: 2.17.0

This is my form type:

<?php

namespace App\Form;

use App\Struct\TestFormStruct;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class TestFormType extends AbstractType
{
    public function __construct(
        private readonly UrlGeneratorInterface $router,
    ) {
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add('testField', ChoiceType::class, [
            'placeholder' => 'Please select...',
            'autocomplete' => true,
            'autocomplete_url' => $this->router->generate('api_test'),
            'options_as_html' => true,
            'no_more_results_text' => '',
        ]);

        $builder->add('save', SubmitType::class);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => TestFormStruct::class,
        ]);
    }
}

This is my TestFormStruct:

<?php

namespace App\Struct;

class TestFormStruct
{
    private ?string $testField = null;

    public function getTestField(): ?string
    {
        return $this->testField;
    }

    public function setTestField(?string $testField): void
    {
        $this->testField = $testField;
    }
}

This is the autocomplete_url action:

#[Route('/test', name: 'api_test', methods: ['GET'])]
public function test(): JsonResponse
{
    return $this->json([
        'results' => [
            'options' => [
                [
                    'value' => '1',
                    'text' => 'Pizza',
                    'group_by' => 'food',
                ],
                [
                    'value' => '2',
                    'text' => 'Banana',
                    'group_by' => 'food',
                ],
            ],
            'optgroups' => [
                [
                    'value' => 'food',
                    'label' => 'food',
                ]
            ],
        ],
    ]);
}

This is my live component:

<?php

declare(strict_types=1);

namespace App\Twig\Components;

use App\Form\TestFormType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\ComponentWithFormTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;

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

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

And this is the template of my live component:

<div {{ attributes }}>
    {{ form_start(form) }}

    {{ form_row(form.testField) }}

    {{ form_end(form) }}
</div>

The component is added to a page with this: <twig:TestForm />

Does anyone have any idea what the problem is?

@smnandre
Copy link
Member

smnandre commented Jun 4, 2024

You should start by setting a LiveProp with the data you want to use in your form (a TestFormStruct instance i guess): https://symfony.com/bundles/ux-live-component/current/index.html#forms

Then, some first questions to maybe give you some ideas / leads #1844

Can you look in the DOM & your browser inspector what is happening ?
When is the error thrown ? What is the initial state ?
Can you submit without changing the field ?
Is your ajax endpoint called ?
Does it work if you use a standard form and not a live one ?
If so, does it work with a Autocomplete with hard-coded choices ?

@momocode-de
Copy link
Author

momocode-de commented Jun 4, 2024

You should start by setting a LiveProp with the data you want to use in your form (a TestFormStruct instance i guess): https://symfony.com/bundles/ux-live-component/current/index.html#forms

Ok, I thought that was optional. I've added it, but unfortunately it hasn't changed anything yet.

When is the error thrown ? What is the initial state ?

When I click in the selection field, a request is sent to the endpoint to load the options. Then the options are displayed and when I select an option, the live component is re-rendered. A request is then sent to the live component and this then fails with a 422 and returns the HTML, which then contains the message “The selected choice is invalid.”. This is the payload that is sent to the endpoint to re-render the live component after I select an option:

{"props":{"initialFormData":{"testField":null},"formName":"test_form","test_form":{"testField":"","save":null,"_token":"..."},"isValidated":false,"validatedFields":[],"@attributes":{"id":"live-3754781061-0"},"@checksum":"..."},"updated":{"test_form.testField":"1","validatedFields":["test_form.testField"]}}

Can you submit without changing the field ?

No, the browser stops me with the message from my second screenshot, because the field is required.

Is your ajax endpoint called ?

Yes, as soon as I click in the field

Does it work if you use a standard form and not a live one ?

Yes, this works, but I need a live form as I need dependent fields in my real case, like in this example.

If so, does it work with a Autocomplete with hard-coded choices ?

Yes, the live form with hardcoded choices works


I found out that I can work around the problem by setting 'required' => false in my field, adding the following line and then validating the field manually when submitting.

$builder->get('testField')->resetViewTransformers();

But I don't think that's a nice solution.


I think the problem is that the options loaded via Ajax are missing in the ChoiceList of the form field. A special “choice_loader” was used for the Entity Autocomplete, which I think must also be done for the Ajax Autocomplete. But I haven't come up with a solution yet.

@smnandre
Copy link
Member

smnandre commented Jun 5, 2024

@momocode-de
Copy link
Author

Maybe try this ? #391 (comment)

No, unfortunately that doesn't help. In my case, it's not about an entity autocomplete. My custom Ajax endpoint, which loads the options, sends a request to an external API. So it would also be bad to have to call the external API again in a custom “choice_loader” (because of rate limits).

Or should I still do it with the 'required' => false and the resetViewTransformers to skip the standard validation and then do it myself when submitting the form? But it would still be nice if there was a clean standard solution for this case that was also documented. It's actually not an unusual case in my opinion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants