symfony / ux

Symfony UX initiative: a JavaScript ecosystem for Symfony
https://ux.symfony.com/
MIT License
820 stars 297 forks source link

[Live Component] The identifier id is missing for a query of ... #311

Closed Faulk13 closed 2 years ago

Faulk13 commented 2 years ago

Hi, I try to test the LiveComponent from SymfonyUX and I have some trouble with my formComponent. I want to add a new object with this form so I think I made the same things like documentation but I have this exception when I try to update my field :

The identifier id is missing for a query of App\Entity\PreparePayslip

Here is my component code :

namespace App\Components;

use App\Entity\PreparePayslip;
use App\Form\PreparePayslipType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\ComponentWithFormTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent('prepare_payslip_form')]
class PreparePayslipFormComponent extends AbstractController
{
    use ComponentWithFormTrait;
    use DefaultActionTrait;

    #[LiveProp(fieldName: 'data')]
    public ?PreparePayslip $preparePayslip = null;

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

My template test for the component :

<div
        {{ attributes }}
        data-action="change->live#update"
>
    {{ form_start(form) }}
        {{ form_rest(form) }}
    {{ form_end(form) }}
</div>

I call this template with :

    {{ component('prepare_payslip_form', {
        preparePayslip: preparePayslip.id ? preparePayslip : null,
        form: form,
    }) }}

And finally my controller :

    #[Route('/preparation-fiche-paie/creer', name: 'prepare_payslip_create')]
    public function create(
        Request $request,
        EntityManagerInterface $entityManager,
        TranslatorInterface $translator,
        UserRepository $userRepository
    ): Response {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');

        /** @var User $user */
        $user = $this->getUser();
        $preparePayslip = new PreparePayslip();
        $preparePayslip->setStatus(PreparePaySlipStatus::DRAFT);
        $preparePayslip->setUser($user);
        if ($request->query->get('impersonateUser')) {
            $impersonateUser = $userRepository->find(['id' => $request->query->get('impersonateUser')]);
            $preparePayslip->setUser($impersonateUser);
        }

        $form = $this->createForm(PreparePayslipType::class, $preparePayslip);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            if ($form->get('validate')->isClicked()) {
                $preparePayslip->setStatus(PreparePaySlipStatus::PENDING_SENDING);
            }
            $preparePayslip->setCreatedAt(new DateTime());

            $entityManager->persist($preparePayslip);
            $entityManager->flush();

            $this->addFlash('success', $translator->trans('flash.prepare_payslip.create.success'));

            return $this->redirectToRoute('dashboard');
        }

        return $this->renderForm('preparePayslip/create.html.twig', [
            'form' => $form,
            'preparePayslip' => $preparePayslip,
        ]);
    }

This form worked before I try to use LiveComponent. I think, I missed something but I can't see where.

Thanks for your help :slightly_smiling_face:

kbond commented 2 years ago

The identifier id is missing for a query of App\Entity\PreparePayslip

Is this exception being thrown by DoctrineObjectNormalizer::denormalize()?

#[LiveProp(fieldName: 'data')]

Can't find it now but thought I saw another issue where using data as a field name was a problem.

Faulk13 commented 2 years ago

Hi, Thanks for your answer,

Is this exception being thrown by DoctrineObjectNormalizer::denormalize()?

Yes exception thrown by DoctrineObjectNormalizer::denormalize().

Can't find it now but thought I saw another issue where using data as a field name was a problem.

Do you have any advice on what I should put in place of "data" ?

Thanks

kbond commented 2 years ago

Do you have any advice on what I should put in place of "data" ?

I was wrong about this - the docs clearly show using data.

When rendering your component, you are injecting preparePayslip?

{{ component('prepare_payslip_form', {
    preparePayslip: preparePayslip,
    form: form
}) }}
Faulk13 commented 2 years ago

When rendering your component, you are injecting preparePayslip?

Yes and as it's creation of new preparePayslip, I follow the documentation to do this :

  {{ component('prepare_payslip_form', {
        preparePayslip: preparePayslip.id ? preparePayslip : null,
        form: form,
    }) }}

If I try your suggestion, i have this exception like the documentation says: An exception has been thrown during the rendering of a template ("Cannot dehydrate an unpersisted entity (App\Entity\PreparePayslip). If you want to allow this, add a dehydrateWith= option to LiveProp.").

kbond commented 2 years ago

Ok, in the docs I see If you need to, you can re-create your new Post() inside of your component.

I'm a little fuzzy on using forms with LiveComponents, but I think this means:

protected function instantiateForm(): FormInterface
{
    return $this->createForm(PreparePayslipType::class, $this->preparePayslip ?? new PreparePayslip());
}
Faulk13 commented 2 years ago

Yes, already tried it too (and I retried just now) but still not working. :sob:

kbond commented 2 years ago

I think I'm wrong about above as well. PreparePayslipType should do this. I think, somehow, we need to get the newly created PreparePayslip on the $preparePayslip property after it's created.

Is this component meant for creation of PreparePayslip's only or after creation, do you want it editable?

Faulk13 commented 2 years ago

Is this component meant for creation of PreparePayslip's only or after creation, do you want it editable?

Yes, I need to create and edit PreparePayslip.

kbond commented 2 years ago

Ok, I'm looking at @weaverryan's demos and I don't know that one is setup explictly showing creation and editing. I thought maybe PostFormComponent but it redirects after saving.

There's nothing in that component that sets Post after creation (because it redirects).

kbond commented 2 years ago

I'm wondering if you change the save action (from the PostFormComponent linked above) to:

    public function save(EntityManagerInterface $entityManager)
    {
        $this->submitForm();

        if (!$this->getFormInstance()->isValid()) {
            // if the form is invalid, allow it to re-render with the errors
            return;
        }

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

        $this->post = $post;

        // no return, would just re-render the component
    }
Faulk13 commented 2 years ago

Ok, I have this on my compent now :

<?php

namespace App\Components;

use App\Entity\PreparePayslip;
use App\Form\PreparePayslipType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\ComponentWithFormTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent('prepare_payslip_form')]
class PreparePayslipFormComponent extends AbstractController
{
    use ComponentWithFormTrait;
    use DefaultActionTrait;

    #[LiveProp(fieldName: 'data')]
    public ?PreparePayslip $preparePayslip = null;

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

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

        if (!$this->getFormInstance()->isValid()) {
            // if the form is invalid, allow it to re-render with the errors
            return;
        }

        /** @var PreparePayslip $preparePayslip */
        $preparePayslip = $this->getFormInstance()->getData();
        $entityManager->persist($preparePayslip);
        $entityManager->flush();

        $this->preparePayslip = $preparePayslip;
    }
}

and Ii add this on my component template but I'm not sure about that:

<div
        {{ attributes }}
        data-action="change->live#update"
>
    {{ form_start(form, {
        attr: {
            'data-action': 'live#action',
            'data-action-name': 'prevent|save'
        }
    }) }}
        {{ form_rest(form) }}
    {{ form_end(form) }}
</div>

Still not working

kbond commented 2 years ago

I'm a bit out of my depth here. @weaverryan, do you have any insights?

Faulk13 commented 2 years ago

I'm a bit out of my depth here

xD no problems and thanks a lot for your help.

I hope, we can find a solution :crossed_fingers:

weaverryan commented 2 years ago

Hey @Faulk13!

Sorry about the trouble! This is actually a bug that was already fixed - #295 - but hasn't been released yet. I'm going to check into getting a tag out.

In the mean-time, you can update your composer.json to this: "symfony/ux-live-component": "2.x-dev", to use the latest version.

Cheers!

Faulk13 commented 2 years ago

Thanks a lot @weaverryan !

It works perfectly now :heart_eyes: