Skip to content

[LiveComponent] resetForm() method to get a fresh form #884

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/LiveComponent/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1506,6 +1506,21 @@ Now, when the form is submitted, it will execute the ``save()`` method
via Ajax. If the form fails validation, it will re-render with the
errors. And if it's successful, it will redirect.

Resetting the Form
~~~~~~~~~~~~~~~~~~

After submitting a form via an action, you might want to "reset" the form
back to its initial state so you can use it again. Do that by calling
``resetForm()`` in your action instead of redirecting::

#[LiveAction]
public function save(EntityManagerInterface $entityManager)
{
// ...

$this->resetForm();
}

Using Actions to Change your Form: CollectionType
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
17 changes: 16 additions & 1 deletion src/LiveComponent/src/ComponentWithFormTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ trait ComponentWithFormTrait
#[LiveProp(writable: true)]
public array $validatedFields = [];

private bool $shouldAutoSubmitForm = true;

/**
* Return the full, top-level, Form object that this component uses.
*/
Expand Down Expand Up @@ -107,7 +109,7 @@ public function initializeForm(array $data): array
#[PreReRender]
public function submitFormOnRender(): void
{
if (!$this->getFormInstance()->isSubmitted()) {
if ($this->shouldAutoSubmitForm) {
$this->submitForm($this->isValidated);
}
}
Expand All @@ -134,6 +136,18 @@ public function getFormName(): string
return $this->formName;
}

/**
* Reset the form to its initial state, so it can be used again.
*/
private function resetForm(): void
{
// prevent the system from trying to submit this reset form
$this->shouldAutoSubmitForm = false;
$this->formInstance = null;
$this->formView = null;
$this->formValues = $this->extractFormValues($this->getForm(), $this->getFormInstance());
}

private function submitForm(bool $validateAll = true): void
{
if (null !== $this->formView) {
Expand All @@ -142,6 +156,7 @@ private function submitForm(bool $validateAll = true): void

$form = $this->getFormInstance();
$form->submit($this->formValues);
$this->shouldAutoSubmitForm = false;

if ($validateAll) {
// mark the entire component as validated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\ComponentWithFormTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\UX\LiveComponent\Tests\Fixtures\Form\FormWithManyDifferentFieldsType;
Expand Down Expand Up @@ -42,4 +43,17 @@ protected function instantiateForm(): FormInterface
$this->initialData
);
}

#[LiveAction]
public function submitAndResetForm()
{
$this->submitForm();
$this->resetForm();
}

#[LiveAction]
public function resetFormWithoutSubmitting()
{
$this->resetForm();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;

/**
* @author Jakub Caban <kuba.iluvatar@gmail.com>
Expand All @@ -33,7 +34,9 @@ public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('text', TextType::class)
->add('textarea', TextareaType::class)
->add('textarea', TextareaType::class, [
'constraints' => [new Length(max: 5, maxMessage: 'textarea is too long')]
])
->add('range', RangeType::class)
->add('choice', ChoiceType::class, [
'choices' => [
Expand Down
57 changes: 57 additions & 0 deletions src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,63 @@ public function testLiveCollectionTypeAddButtonsByDefault(): void
;
}

public function testResetForm(): void
{
$mounted = $this->mountComponent('form_with_many_different_fields_type');

$dehydratedProps = $this->dehydrateComponent($mounted)->getProps();

$getUrl = function (array $props, array $updatedProps = null) {
$url = '/_components/form_with_many_different_fields_type?props='.urlencode(json_encode($props));
if (null !== $updatedProps) {
$url .= '&updated='.urlencode(json_encode($updatedProps));
}

return $url;
};

$browser = $this->browser();
$crawler = $browser
->get($getUrl($dehydratedProps, [
'form' => [
'text' => 'foo',
'textarea' => 'longer than 5',
],
'validatedFields' => ['form.text', 'form.textarea'],
]))
->assertStatus(422)
->assertContains('textarea is too long')
->crawler()
;

$div = $crawler->filter('[data-controller="live"]');
$dehydratedProps = json_decode($div->attr('data-live-props-value'), true);
$token = $div->attr('data-live-csrf-value');

$browser
->post('/_components/form_with_many_different_fields_type/submitAndResetForm', [
'body' => json_encode([
'props' => $dehydratedProps,
'updated' => ['form.textarea' => 'short'],
]),
'headers' => ['X-CSRF-TOKEN' => $token],
])
->assertStatus(200)
->assertContains('<textarea id="form_textarea" name="form[textarea]" required="required"></textarea>')
;

// try resetting without submitting
$browser
->post('/_components/form_with_many_different_fields_type/resetFormWithoutSubmitting', [
'body' => json_encode(['props' => $dehydratedProps]),
'headers' => ['X-CSRF-TOKEN' => $token],
])
->assertStatus(200)
->assertNotContains('textarea is too long')
->assertContains('<textarea id="form_textarea" name="form[textarea]" required="required"></textarea>')
;
}

public function testLiveCollectionTypeFieldsAddedAndRemoved(): void
{
$dehydratedProps = $this->dehydrateComponent($this->mountComponent('form_with_live_collection_type'))->getProps();
Expand Down
23 changes: 5 additions & 18 deletions src/TwigComponent/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -294,11 +294,8 @@ PostMount Hook

The ``PostMount`` hook was added in TwigComponents 2.1.

When a component is mounted with the passed data, if an item cannot be
mounted on the component, an exception is thrown. You can intercept this
behavior and "catch" this extra data with a ``PostMount`` hook method. This
method accepts the extra data as an argument and must return an array. If
the returned array is empty, the exception will be avoided::
After a component is instantiated and its data mounted, you can run extra
code via the ``PostMount`` hook::

// src/Components/Alert.php
use Symfony\UX\TwigComponent\Attribute\PostMount;
Expand All @@ -319,18 +316,8 @@ the returned array is empty, the exception will be avoided::

A ``PostMount`` method can also receive an array ``$data`` argument, which
will contain any props passed to the component that have *not* yet been processed,
i.e. because they don't correspond to any property. You can handle and remove those
here. For example, imagine an extra ``autoChooseType`` prop were passed when
creating the ``Alert`` component:

.. code-block:: twig

{{ component('Alert', {
message: 'Danger Will Robinson!',
autoChooseType: true,
}) }}

You can handle this prop via a ``#[PostMount]`` hook::
i.e. because they don't correspond to any property. You can handle these props,
remove them from the ``$data`` and return the array::

// src/Components/Alert.php
#[AsTwigComponent]
Expand All @@ -342,7 +329,7 @@ You can handle this prop via a ``#[PostMount]`` hook::
#[PostMount]
public function processAutoChooseType(array $data): array
{
if (array_key_exists('autoChooseType', $data) && $data['autoChooseType']) {
if ($data['autoChooseType'] ?? false) {
if (str_contains($this->message, 'danger')) {
$this->type = 'danger';
}
Expand Down