Skip to content

Commit d4042f7

Browse files
committed
[LiveComponent] Adding a resetForm() method to the form trait to get a fresh form
1 parent b9ec3f7 commit d4042f7

File tree

5 files changed

+106
-2
lines changed

5 files changed

+106
-2
lines changed

src/LiveComponent/doc/index.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,6 +1506,21 @@ Now, when the form is submitted, it will execute the ``save()`` method
15061506
via Ajax. If the form fails validation, it will re-render with the
15071507
errors. And if it's successful, it will redirect.
15081508

1509+
Resetting the Form
1510+
~~~~~~~~~~~~~~~~~~
1511+
1512+
After submitting a form via an action, you might want to "reset" the form
1513+
back to its initial state so you can use it again. Do that by calling
1514+
``resetForm()`` in your action instead of redirecting::
1515+
1516+
#[LiveAction]
1517+
public function save(EntityManagerInterface $entityManager)
1518+
{
1519+
// ...
1520+
1521+
$this->resetForm();
1522+
}
1523+
15091524
Using Actions to Change your Form: CollectionType
15101525
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
15111526

src/LiveComponent/src/ComponentWithFormTrait.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ trait ComponentWithFormTrait
6262
#[LiveProp(writable: true)]
6363
public array $validatedFields = [];
6464

65+
private bool $shouldAutoSubmitForm = true;
66+
6567
/**
6668
* Return the full, top-level, Form object that this component uses.
6769
*/
@@ -107,7 +109,7 @@ public function initializeForm(array $data): array
107109
#[PreReRender]
108110
public function submitFormOnRender(): void
109111
{
110-
if (!$this->getFormInstance()->isSubmitted()) {
112+
if ($this->shouldAutoSubmitForm) {
111113
$this->submitForm($this->isValidated);
112114
}
113115
}
@@ -134,6 +136,18 @@ public function getFormName(): string
134136
return $this->formName;
135137
}
136138

139+
/**
140+
* Reset the form to its initial state, so it can be used again.
141+
*/
142+
private function resetForm(): void
143+
{
144+
// prevent the system from trying to submit this reset form
145+
$this->shouldAutoSubmitForm = false;
146+
$this->formInstance = null;
147+
$this->formView = null;
148+
$this->formValues = $this->extractFormValues($this->getForm(), $this->getFormInstance());
149+
}
150+
137151
private function submitForm(bool $validateAll = true): void
138152
{
139153
if (null !== $this->formView) {
@@ -142,6 +156,7 @@ private function submitForm(bool $validateAll = true): void
142156

143157
$form = $this->getFormInstance();
144158
$form->submit($this->formValues);
159+
$this->shouldAutoSubmitForm = false;
145160

146161
if ($validateAll) {
147162
// mark the entire component as validated

src/LiveComponent/tests/Fixtures/Component/FormComponentWithManyDifferentFieldsType.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Form\FormFactoryInterface;
1616
use Symfony\Component\Form\FormInterface;
1717
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
18+
use Symfony\UX\LiveComponent\Attribute\LiveAction;
1819
use Symfony\UX\LiveComponent\ComponentWithFormTrait;
1920
use Symfony\UX\LiveComponent\DefaultActionTrait;
2021
use Symfony\UX\LiveComponent\Tests\Fixtures\Form\FormWithManyDifferentFieldsType;
@@ -42,4 +43,17 @@ protected function instantiateForm(): FormInterface
4243
$this->initialData
4344
);
4445
}
46+
47+
#[LiveAction]
48+
public function submitAndResetForm()
49+
{
50+
$this->submitForm();
51+
$this->resetForm();
52+
}
53+
54+
#[LiveAction]
55+
public function resetFormWithoutSubmitting()
56+
{
57+
$this->resetForm();
58+
}
4559
}

src/LiveComponent/tests/Fixtures/Form/FormWithManyDifferentFieldsType.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Symfony\Component\Form\Extension\Core\Type\TextType;
2424
use Symfony\Component\Form\FormBuilderInterface;
2525
use Symfony\Component\OptionsResolver\OptionsResolver;
26+
use Symfony\Component\Validator\Constraints\Length;
2627

2728
/**
2829
* @author Jakub Caban <[email protected]>
@@ -33,7 +34,9 @@ public function buildForm(FormBuilderInterface $builder, array $options)
3334
{
3435
$builder
3536
->add('text', TextType::class)
36-
->add('textarea', TextareaType::class)
37+
->add('textarea', TextareaType::class, [
38+
'constraints' => [new Length(max: 5, maxMessage: 'textarea is too long')]
39+
])
3740
->add('range', RangeType::class)
3841
->add('choice', ChoiceType::class, [
3942
'choices' => [

src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,63 @@ public function testLiveCollectionTypeAddButtonsByDefault(): void
261261
;
262262
}
263263

264+
public function testResetForm(): void
265+
{
266+
$mounted = $this->mountComponent('form_with_many_different_fields_type');
267+
268+
$dehydratedProps = $this->dehydrateComponent($mounted)->getProps();
269+
270+
$getUrl = function (array $props, array $updatedProps = null) {
271+
$url = '/_components/form_with_many_different_fields_type?props='.urlencode(json_encode($props));
272+
if (null !== $updatedProps) {
273+
$url .= '&updated='.urlencode(json_encode($updatedProps));
274+
}
275+
276+
return $url;
277+
};
278+
279+
$browser = $this->browser();
280+
$crawler = $browser
281+
->get($getUrl($dehydratedProps, [
282+
'form' => [
283+
'text' => 'foo',
284+
'textarea' => 'longer than 5',
285+
],
286+
'validatedFields' => ['form.text', 'form.textarea'],
287+
]))
288+
->assertStatus(422)
289+
->assertContains('textarea is too long')
290+
->crawler()
291+
;
292+
293+
$div = $crawler->filter('[data-controller="live"]');
294+
$dehydratedProps = json_decode($div->attr('data-live-props-value'), true);
295+
$token = $div->attr('data-live-csrf-value');
296+
297+
$browser
298+
->post('/_components/form_with_many_different_fields_type/submitAndResetForm', [
299+
'body' => json_encode([
300+
'props' => $dehydratedProps,
301+
'updated' => ['form.textarea' => 'short']
302+
]),
303+
'headers' => ['X-CSRF-TOKEN' => $token],
304+
])
305+
->assertStatus(200)
306+
->assertContains('<textarea id="form_textarea" name="form[textarea]" required="required"></textarea>')
307+
;
308+
309+
// try resetting without submitting
310+
$browser
311+
->post('/_components/form_with_many_different_fields_type/resetFormWithoutSubmitting', [
312+
'body' => json_encode(['props' => $dehydratedProps]),
313+
'headers' => ['X-CSRF-TOKEN' => $token],
314+
])
315+
->assertStatus(200)
316+
->assertNotContains('textarea is too long')
317+
->assertContains('<textarea id="form_textarea" name="form[textarea]" required="required"></textarea>')
318+
;
319+
}
320+
264321
public function testLiveCollectionTypeFieldsAddedAndRemoved(): void
265322
{
266323
$dehydratedProps = $this->dehydrateComponent($this->mountComponent('form_with_live_collection_type'))->getProps();

0 commit comments

Comments
 (0)