symfony / ux

Symfony UX initiative: a JavaScript ecosystem for Symfony

Home Page:https://ux.symfony.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[LiveComponent] Using a DTO for a search form

YummYume opened this issue · comments

commented

Hello !

This issue is more like a question that I feel like is not mentioned anywhere in the docs. Traditionally, when making a search form with filters (for example, let's say a global search), I would use a DTO class specially made for that, with each property being an input to the user.

When using a live component, I struggle to find the "right" way to make my component work with a form + a DTO. In the docs, the very first example is a search, but it's using properties directly in the component. Later on, form examples only use entities and show how the submit works with actions, but not how it could work with a simple DTO. The reason I want to use a form in this case is mostly for the ease of use, the already existing form themes, and it would even allow older apps with old searches to be upgraded very easily, by keeping the same DTO.

There are many ways this could be done, for example listen to input events and call an action every time. But I'd like to have it easy, and just have my component automatically re-render when the user types something, or changes a field, and have my DTO update depending on my form. The easiest solution I found was to add 'data-model': 'on(input)|*' to my form, but this causes issues with my DTO in many cases. Here's an example :

class GlobalSearchDTO
{
    public ?string $query = null;
}
#[AsLiveComponent]
class GlobalSearch extends AbstractController
{
    use ComponentWithFormTrait;
    use DefaultActionTrait;

    #[LiveProp]
    public GlobalSearchDTO $globalSearchDTO;

    public function __construct( ) {
        // This is the only way I found to make this "work".
        // Not instantiating the $globalSearchDTO causes an error, obviously
        // Setting it to "null" by default causes the value to never change
        $this->globalSearchDTO = new GlobalSearchDTO();
    }

    public function getResults(): array
    {
        $query = $this->globalSearchDTO->query;
        
       // Get the results...

        return $results;
    }

    protected function instantiateForm(): FormInterface
    {
        return $this->createForm(GlobalSearchType::class, $this->globalSearchDTO);
    }
}
<div {{ attributes }}>
    <div class="flex gap-2.5 items-center">
        {{ form_start(form, {attr: {
            class: 'grow',
            role: 'search',
            'data-model': 'on(input)|*'
        }}) }}
            {# The form... #}
        {{ form_end(form) }}
    </div>

    {# ... #}
</div>

The form itself is a simple form with a simple SearchType for the query.

I'm fairly certain that what I'm doing is a hacky way to get things done, which I don't like. I would love to hear opinions on this and settle on the right approach to get a simple search working with just a DTO and a form.

Yes this is the right way do it. What in your code feel hacky to you?

The form itself is a simple form with a simple SearchType for the query.

In this case you should do without a form.

This is the simplest and the best demo to showcase LiveComponent: a live search, no form, only one class: https://ux.symfony.com/live-component

commented

@WebMamba What feels pretty hacky to me is the fact that I'm mixing a form with a DTO, but without actually ever submitting the form, just using the data-model to refresh the component on any input.

@smnandre That was more of an example for a simple case, but I do have much more complicated DTOs with many filters, many of which would be a pain to make work without a form (themes, data transformers, options and so on...)