diff --git a/src/LiveComponent/src/ComponentWithFormTrait.php b/src/LiveComponent/src/ComponentWithFormTrait.php index a25a2d1dc02..f7a7caddcdb 100644 --- a/src/LiveComponent/src/ComponentWithFormTrait.php +++ b/src/LiveComponent/src/ComponentWithFormTrait.php @@ -110,12 +110,17 @@ public function submitFormOnRender(): void { if ($this->shouldAutoSubmitForm) { $this->submitForm($this->isValidated); + } else { + // Recreate the FormView because it has been created by the submitForm() with a FormInterface whose values may + // have changed. Basically synchronizes FormView and FormInstance to reflect all manual changes made to the + // latter between form submit and the components re-render. + $this->getFormView(true); } } - public function getFormView(): FormView + public function getFormView(bool $forceCreate = false): FormView { - if (null === $this->formView) { + if ($forceCreate || null === $this->formView) { $this->formView = $this->getForm()->createView(); $this->useNameAttributesAsModelName(); } diff --git a/src/LiveComponent/tests/Fixtures/Component/FormWithCollectionTypeComponent.php b/src/LiveComponent/tests/Fixtures/Component/FormWithCollectionTypeComponent.php index 1686b9a9987..ca86b74209c 100644 --- a/src/LiveComponent/tests/Fixtures/Component/FormWithCollectionTypeComponent.php +++ b/src/LiveComponent/tests/Fixtures/Component/FormWithCollectionTypeComponent.php @@ -12,7 +12,9 @@ namespace Symfony\UX\LiveComponent\Tests\Fixtures\Component; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; use Symfony\UX\LiveComponent\Attribute\LiveAction; use Symfony\UX\LiveComponent\Attribute\LiveArg; @@ -42,6 +44,14 @@ protected function instantiateForm(): FormInterface return $this->createForm(BlogPostFormType::class, $this->post); } + #[LiveAction] + public function submitAndAddErrorToForm(): void + { + $this->submitForm(); + $this->getForm()->addError(new FormError("manually added form error")); + throw new UnprocessableEntityHttpException(); + } + #[LiveAction] public function addComment() { diff --git a/src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php b/src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php index bfbe477c948..b504bd17d9c 100644 --- a/src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php +++ b/src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php @@ -156,6 +156,33 @@ public function testFormRemembersValidationFromInitialForm(): void ; } + public function testFormViewSynchronizesWithFormInstance(): void + { + /** @var FormFactoryInterface $formFactory */ + $formFactory = self::getContainer()->get('form.factory'); + + $form = $formFactory->create(BlogPostFormType::class); + // make sure validation does not fail on content constraint (min 100 characters) + $validContent = implode('a', range(0, 100)); + $form->submit(['title' => 'Title', 'content' => $validContent]); + + $mounted = $this->mountComponent('form_with_collection_type', [ + 'form' => $form->createView(), + ]); + $dehydratedProps = $this->dehydrateComponent($mounted)->getProps(); + + $this->browser() + // post to action, which will manually add a FormError to the FormInstance after submit + ->post('/_components/form_with_collection_type/submitAndAddErrorToForm', [ + 'body' => ['data' => json_encode(['props' => $dehydratedProps])], + ]) + // action always throws 422 + ->assertStatus(422) + // assert manually added error within LiveAction after submit is rendered in template + ->assertContains('manually added form error') + ; + } + public function testHandleCheckboxChanges(): void { $category = CategoryFixtureEntityFactory::createMany(5);