Skip to content

Commit c8b3a28

Browse files
committed
[Live] Re-extracting form data after submit
1 parent 0c6423c commit c8b3a28

File tree

14 files changed

+729
-63
lines changed

14 files changed

+729
-63
lines changed

src/LiveComponent/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
- Added `data-live-ignore` attribute. If included in an element, that element
66
will not be updated on re-render.
77

8+
- `ComponentWithFormTrait` no longer has a `setForm()` method. But there
9+
is also no need to call it anymore. To pass an already-built form to
10+
your component, pass it as a `form` var to `component()`. If you have
11+
a custom `mount()`, you no longer need to call `setForm()` or anything else.
12+
813
- The Live Component AJAX endpoints now return HTML in all situations
914
instead of JSON.
1015

src/LiveComponent/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"doctrine/doctrine-bundle": "^2.0",
3535
"doctrine/orm": "^2.7",
3636
"symfony/dependency-injection": "^5.4|^6.0",
37+
"symfony/form": "^5.4|^6.0",
3738
"symfony/framework-bundle": "^5.4|^6.0",
3839
"symfony/phpunit-bridge": "^6.0",
3940
"symfony/security-csrf": "^5.4|^6.0",

src/LiveComponent/src/ComponentWithFormTrait.php

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
1818
use Symfony\UX\LiveComponent\Attribute\BeforeReRender;
1919
use Symfony\UX\LiveComponent\Attribute\LiveProp;
20+
use Symfony\UX\LiveComponent\Util\LiveFormUtility;
21+
use Symfony\UX\TwigComponent\Attribute\PostMount;
2022

2123
/**
2224
* @author Ryan Weaver <[email protected]>
@@ -63,17 +65,29 @@ trait ComponentWithFormTrait
6365
*/
6466
abstract protected function instantiateForm(): FormInterface;
6567

66-
/**
67-
* Override in your class if you need extra mounted values.
68-
*
69-
* Call $this->setForm($form) manually in that situation
70-
* if you're passing in an initial form.
71-
*/
72-
public function mount(?FormView $form = null)
68+
#[PostMount]
69+
public function postMount(array $data): array
7370
{
74-
if ($form) {
75-
$this->setForm($form);
71+
// allow the FormView object to be passed into the component() as "form"
72+
if (\array_key_exists('form', $data)) {
73+
$this->formView = $data['form'];
74+
unset($data['form']);
75+
76+
if ($this->formView) {
77+
// if a FormView is passed in and it contains any errors, then
78+
// we mark that this entire component has been validated so that
79+
// all validation errors continue showing on re-render
80+
if (LiveFormUtility::doesFormContainAnyErrors($this->formView)) {
81+
$this->isValidated = true;
82+
$this->validatedFields = [];
83+
}
84+
}
7685
}
86+
87+
// set the formValues from the initial form view's data
88+
$this->initializeFormValues();
89+
90+
return $data;
7791
}
7892

7993
/**
@@ -87,7 +101,7 @@ public function mount(?FormView $form = null)
87101
public function submitFormOnRender(): void
88102
{
89103
if (!$this->getFormInstance()->isSubmitted()) {
90-
$this->submitForm(false);
104+
$this->submitForm($this->isValidated);
91105
}
92106
}
93107

@@ -103,18 +117,6 @@ public function getForm(): FormView
103117
return $this->formView;
104118
}
105119

106-
/**
107-
* Call this from mount() if your component receives a FormView.
108-
*
109-
* If your are not passing a FormView into your component, you
110-
* don't need to call this directly: the form will be set for
111-
* you from your instantiateForm() method.
112-
*/
113-
public function setForm(FormView $form): void
114-
{
115-
$this->formView = $form;
116-
}
117-
118120
public function getFormName(): string
119121
{
120122
if (!$this->formName) {
@@ -124,18 +126,19 @@ public function getFormName(): string
124126
return $this->formName;
125127
}
126128

127-
public function getFormValues(): array
129+
private function initializeFormValues(): void
128130
{
129-
if (null === $this->formValues) {
130-
$this->formValues = $this->extractFormValues($this->getForm());
131-
}
132-
133-
return $this->formValues;
131+
$this->formValues = $this->extractFormValues($this->getForm());
134132
}
135133

136134
private function submitForm(bool $validateAll = true): void
137135
{
138-
$this->getFormInstance()->submit($this->formValues);
136+
if (null !== $this->formView) {
137+
throw new \LogicException('The submitForm() method is being called, but the FormView has already been built. Are you calling $this->getForm() - which creates the FormView - before submitting the form?');
138+
}
139+
140+
$form = $this->getFormInstance();
141+
$form->submit($this->formValues);
139142

140143
if ($validateAll) {
141144
// mark the entire component as validated
@@ -146,10 +149,19 @@ private function submitForm(bool $validateAll = true): void
146149
// we only want to validate fields in validatedFields
147150
// but really, everything is validated at this point, which
148151
// means we need to clear validation on non-matching fields
149-
$this->clearErrorsForNonValidatedFields($this->getFormInstance(), $this->getFormName());
152+
$this->clearErrorsForNonValidatedFields($form, $form->getName());
150153
}
151154

152-
if (!$this->getFormInstance()->isValid()) {
155+
// re-extract the "view" values in case the submitted data
156+
// changed the underlying data or structure of the form
157+
$this->formValues = $this->extractFormValues($this->getForm());
158+
// remove any validatedFields that do not exist in data anymore
159+
$this->validatedFields = LiveFormUtility::removePathsNotInData(
160+
$this->validatedFields ?? [],
161+
[$form->getName() => $this->formValues],
162+
);
163+
164+
if (!$form->isValid()) {
153165
throw new UnprocessableEntityHttpException('Form validation failed in component');
154166
}
155167
}
@@ -192,7 +204,7 @@ private function getFormInstance(): FormInterface
192204
return $this->formInstance;
193205
}
194206

195-
private function clearErrorsForNonValidatedFields(Form $form, $currentPath = ''): void
207+
private function clearErrorsForNonValidatedFields(Form $form, string $currentPath = ''): void
196208
{
197209
if (!$currentPath || !\in_array($currentPath, $this->validatedFields, true)) {
198210
$form->clearErrors();

0 commit comments

Comments
 (0)