Skip to content

[Live] Re-extracting form data after submit #254

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
Feb 2, 2022

Conversation

weaverryan
Copy link
Member

@weaverryan weaverryan commented Jan 28, 2022

Q A
Bug fix? yes
New feature? yes
Tickets Addresses part of #221
License MIT

This fixes 3 bugs at once:

  1. This recalculates formValues after submit, in case the submitted data changes the form's data or underlying structure (e.g. CollectionType)

  2. If a field is suddenly missing entirely from a form (hint: think removing an embedded CollectionType form), then previously validatedFields did not "forget" that field. This caused edge-case bug when removing an embedded form that was validated, then adding a new form a moment later (which would be empty, but now validated).

  3. When passing a FormView into your component that was already validated, on the first re-render, the validation would be lost and only modified fields would be validated.

Ping @Lustmored and @norkunas. This mostly addresses @norkunas's problems with how getFormValues() works, but this was also causing trouble for @Lustmored, potentially for different reasons. So, this may not solve the problems addressed in #221, but it's certainly related.

Cheers!

@norkunas
Copy link
Contributor

So if I'll have hundred live actions I'll need to write same number submit/reset statements? :)

@weaverryan
Copy link
Member Author

weaverryan commented Jan 29, 2022

So if I'll have hundred live actions I'll need to write same number submit/reset statements? :)

Yup! The same as if you has a hundred controller actions 😉

BUT, I'm re-thinking this approach - I think it's flawed, as trying to "submit the form" then "change the data the form was created from" then... re-recreate the form with the new mix of data... just doesn't work, as the final "new" form wouldn't be "submitted", so it wouldn't have correct validation errors. If we re-submit the 2nd form, then we overwrite the new values we just changed in our underlying object. I think it's a dead approach.

I think the easier flow is to, in your actions, manipulate $formValues directly. This, I believe, is actually what you were doing all along @norkunas - I was trying to do something fancier. It doesn't look as neat as manipulating the underlying form data DTO/entity, but it's much simpler. The flow in an action would be what you have currently @norkunas (I'm just arriving to the solution later). I still need to try it, and see if my fix for getFormValues() still works with your approach. Will check into it next week.

@norkunas
Copy link
Contributor

just doesn't work, as the final "new" form wouldn't be "submitted", so it wouldn't have correct validation errors.

Yeah, at least I currently get them 😉

@Lustmored
Copy link
Contributor

From a quick glimpse I can tell that this does not collide nor solve the problem addressed in my PR, which focuses on handling different kind of checkbox field types within forms 👍

@weaverryan weaverryan changed the title Adding refreshForm() to ComponentWithFormTrait [Live] Re-extracting form data after submit Jan 31, 2022
$form->submit($this->formValues);
// re-extract the "view" values in case the submitted data
// changed the underlying data or structure of the form
$this->formValues = $this->extractFormValues($this->getForm());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the key. It's super simple, it just took me a long time to understand what's going on. The flow is this:

A) A custom action is called
B) In the custom action, the user changes $this->formValues. For example, to add a new array to initialize a new collection type.
C) We re-extract the formValues from the form.

Normally, if the user sends formValues and we then use those to submit the form, the formValues that should be sent back to the frontend would be the same: that is STILL the "formValues" data. However, if your form changes as a result of submitted data (e.g. CollectionType which adds more fields if you add a new item to a collection type array), then we need to re-extract the formValues so that they match the new reality.

}

return $this->formValues;
$this->formValues = $this->extractFormValues($this->getForm());
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything above this was just cleaning up the flow, but not significant

@weaverryan
Copy link
Member Author

Other than a small TODO in the documentation, this is ready to go. This is much simpler now & mirrors what @norkunas was doing in his project, except now with the fix to formValues. It mentally took me awhile to get the clarity that this small solution was needed (I previously had a much bigger solution in this PR).

So if I'll have hundred live actions I'll need to write same number submit/reset statements? :)

So now the answer to that is "no": your actions modify formValues and nothing else.

* If you override, call $this->initializeFormValues() manually
* and also $this->formView = $form if you are passing in a Form.
*
* TODO: Refactor to PostMount when available.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostMount is now available.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeeeea it is 😎

@weaverryan
Copy link
Member Author

This is now ready. The form process is now much smoother, working with CollectionType is a wonderful experience.

@kbond kbond mentioned this pull request Feb 1, 2022
1 task

public static function doesFormContainAnyErrors(FormView $formView): bool
{
if ($formView->vars['errors'] ?? null && \count($formView->vars['errors']) > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe?:

Suggested change
if ($formView->vars['errors'] ?? null && \count($formView->vars['errors']) > 0) {
if (($formView->vars['errors'] ?? null) && \count($formView->vars['errors']) > 0) {

As EA hints about:
image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure - but your suggestion is safer 👍

@weaverryan weaverryan merged commit 6715a03 into symfony:2.x Feb 2, 2022
@weaverryan weaverryan deleted the allow-form-reset branch February 2, 2022 18:23
weaverryan added a commit that referenced this pull request Feb 14, 2022
This PR was squashed before being merged into the 2.x branch.

Discussion
----------

[Twig] add `ExposeInTemplate` attribute

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| Tickets       | n/a
| License       | MIT

#232 made all of a component's public properties available directly (without the `this.`) in it's template. This adds an `ExposeInTemplate` attribute to make non-public properties available in this way.

Example:

```php
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;

#[AsTwigComponent('with_exposed_variables')]
final class WithExposedVariables
{
    #[ExposeInTemplate]
    private string $prop1; // available as `{{ prop1 }}`

    #[ExposeInTemplate('customProp')]
    private string $prop2; // available as `{{ customProp }}`

    #[ExposeInTemplate(name: 'ico', getter: 'fetchIcon')]
    private string $icon = 'ico-warning'; // available as `{{ ico }}` in the template using `fetchIcon()` as the getter

    /**
     * Required to access $prop1
     */
    public function getProp1(): string
    {
        return $this->prop1;
    }

    /**
     * Required to access $prop1
     */
    public function getProp2(): string
    {
        return $this->prop2;
    }

    /**
     * Required to access $this->icon
     */
    public function fetchIcon(): string
    {
        return $this->icon;
    }
}
```

**TODO:**
- [x] use to expose `form` in `ComponentWithFormTrait` (requires #254)

Commits
-------

e6c53fd [Twig] add `ExposeInTemplate` attribute
fullstackdeveloperwebapp added a commit to fullstackdeveloperwebapp/ux that referenced this pull request Aug 1, 2023
This PR was squashed before being merged into the 2.x branch.

Discussion
----------

[Twig] add `ExposeInTemplate` attribute

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| Tickets       | n/a
| License       | MIT

#232 made all of a component's public properties available directly (without the `this.`) in it's template. This adds an `ExposeInTemplate` attribute to make non-public properties available in this way.

Example:

```php
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;

#[AsTwigComponent('with_exposed_variables')]
final class WithExposedVariables
{
    #[ExposeInTemplate]
    private string $prop1; // available as `{{ prop1 }}`

    #[ExposeInTemplate('customProp')]
    private string $prop2; // available as `{{ customProp }}`

    #[ExposeInTemplate(name: 'ico', getter: 'fetchIcon')]
    private string $icon = 'ico-warning'; // available as `{{ ico }}` in the template using `fetchIcon()` as the getter

    /**
     * Required to access $prop1
     */
    public function getProp1(): string
    {
        return $this->prop1;
    }

    /**
     * Required to access $prop1
     */
    public function getProp2(): string
    {
        return $this->prop2;
    }

    /**
     * Required to access $this->icon
     */
    public function fetchIcon(): string
    {
        return $this->icon;
    }
}
```

**TODO:**
- [x] use to expose `form` in `ComponentWithFormTrait` (requires symfony/ux#254)

Commits
-------

e6c53fd [Twig] add `ExposeInTemplate` attribute
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants