Search code examples
phpsymfonysymfonyux

Symfony-ux-live-component No matching "form" property or mount() argument was found


class PostForm extends AbstractController
{
    use DefaultActionTrait;
    use ComponentWithFormTrait;

    /**
     * The initial data used to create the form.
     */
    #[LiveProp]
    public ?Post $initialFormData = null;

    protected function instantiateForm(): FormInterface
    {
        // we can extend AbstractController to get the normal shortcuts
        return $this->createForm(PostType::class, $this->initialFormData);
    }

    #[LiveAction]
    public function save(EntityManagerInterface $entityManager)
    {
        // Submit the form! If validation fails, an exception is thrown
        // and the component is automatically re-rendered with the errors
        $this->submitForm();

        /** @var Post $post */
        $post = $this->getForm()->getData();
        $entityManager->persist($post);
        $entityManager->flush();

        $this->addFlash('success', 'Post saved!');

        return $this->redirectToRoute('app_post_show', [
            'id' => $post->getId(),
        ]);
    }
}
class Post
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $title = null;

    #[ORM\Column(length: 255)]
    private ?string $slug = null;

    #[ORM\Column(type: Types::TEXT)]
    private ?string $content = null;


..........

}
class PostType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('title')
            ->add('slug')
            ->add('content')
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Post::class,
        ]);
    }
}
class PostController extends AbstractController
{
    #[Route('/post', name: 'app_post')]
    public function index(): Response
    {
        return $this->render('post/index.html.twig', [
            'controller_name' => 'PostController',
            'form' => $this->createForm(PostType::class, new Post())
        ]);
    }

    #[Route('/admin/post/{id}/edit', name: 'app_post_edit')]
    public function edit(Request $request, Post $post, EntityManagerInterface $entityManager): Response
    {
        $form = $this->createForm(PostType::class, $post);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            // save, redirect, etc
        }

        return $this->render('post/edit.html.twig', [
            'post' => $post,
            'form' => $form->createView(), // use $form->createView() in Symfony <6.2
        ]);
    }

}
{# PostForm.html.twig #}

<div {{ attributes }}>
    {{ form_start(form, {
        attr: {
            'data-action': 'live#action:prevent',
            'data-live-action-param': 'save'
        }
    }) }}
    {{ form_row(form.title) }}
    {{ form_row(form.slug) }}
    {{ form_row(form.content) }}

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

{# index.html.twig #}

{% extends 'base.html.twig' %}

{% block title %}Hello PostController!{% endblock %}

{% block body %}
<style>
    .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
    .example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>

<div class="example-wrapper">
    <h1>Hello {{ controller_name }}! ✅</h1>

    This friendly message is coming from:
</div>

    {{ component('PostForm', {
        form: form,
    }) }}
{% endblock %}
{#  edit.html.twig #}

{% extends 'base.html.twig' %}

{% block body %}
    <h1>Edit Post</h1>

    {{ component('PostForm', {
        initialFormData: post,
        form: form
    }) }}
{% endblock %}

when I try to validate a form through ux-live-component I have this error:

An exception has been thrown during the rendering of a template ("A "form" prop was passed when creating the component. No matching "form" property or mount() argument was found, so we attempted to use this as an HTML attribute. But, the value is not a scalar (it's a "Symfony\Component\Form\FormView"). Did you mean to pass this to your component or is there a typo on its name?").

in C:\Users\Onesine\SymfonyProjects\Demo\templates\components\PostForm.html.twig (line 1)

--> here <div {{ attributes }}>
{{ form_start(form, {attr: {'data-action': 'live#action:prevent','data-live-action-param': 'save'}

Solution

  • There are a few misconceptions in your code. The component should handle the form creation, validation and - if you like - submission to benefit from e.g. live validation. That is it's greatest strength. When you handle form creation, validation and submission of a form in a regular controller, you lose live validation (as well as other benefits).

    You're creating the form in the UX Live Component AND in your PostController - and then passing the form to the component, which isn't designed to handle a form this way.

    protected function instantiateForm(): FormInterface
    {
        // we can extend AbstractController to get the normal shortcuts
        return $this->createForm(PostType::class, $this->initialFormData);
    }
    

    The code above makes the form available in your twig template. So you can delete all form creation and submission code from your PostController, so it looks like this:

    class PostController extends AbstractController
    {
        #[Route('/post', name: 'app_post')]
        public function index(): Response
        {
            return $this->render('post/index.html.twig', [
                'controller_name' => 'PostController',
            ]);
        }
    
        #[Route('/admin/post/{id}/edit', name: 'app_post_edit')]
        public function edit(Request $request, Post $post, EntityManagerInterface $entityManager): Response
        {
            return $this->render('post/edit.html.twig', [
                'post' => $post,
            ]);
        }
    

    Also remove the passing of 'form' props to the components.

    {{ component('PostForm', {
        initialFormData: post,
        form: form <---- remove. the component has the form.
    }) }}
    

    This should fix the issue with the form. It is advisable to carefully read the UX Live Component docs on this topic: https://symfony.com/bundles/ux-live-component/current/index.html#forms