ciscoheat / sveltekit-superforms

Making SvelteKit forms a pleasure to use!
https://superforms.rocks
MIT License
2.26k stars 65 forks source link

preserved validation results/errors for array items #508

Closed linus-amg closed 1 week ago

linus-amg commented 2 weeks ago

https://github.com/user-attachments/assets/05e2a674-b6d8-4bfe-b01f-167cf2c385f2

Description Validation results seem to stay in some kind of cache when an invalid element gets deleted from an array, while the expectation is that the errors of this item would also get reset.

MRE: https://www.sveltelab.dev/nl0wj3phemks2d5 Instructions: 1) Click add button to add a new item to an array 2) Click submit button to trigger client-side validation 3) See the "Required" error message next to the input field 4) Click delete button to delete the item from the array 5) Click add button 6) See the "Required" error message next to the input field, even though we did not yet submit again - it seems as the index is stuck with the validation error from before 7) Click add one more time 8) See that the last item added does not have a validation error

Same results with trying all other validationMethod.

pekeler commented 1 week ago

Related question:

Once the user tries to submit a form, all fields are validated. In case where the submission fails due to validation errors, the user could make a change afterwards that reveals more form fields (perhaps by changing a "kind" attribute which controls a discriminating union in the zod schema).

One could argue that the newly revealed fields should immediately be validated to be consistent with the fields that were already there before the attempted submit. Or, one could argue that they should not be validated until the user actually focussed->inputted->blurred something on the new field.

Does Superforms have an opinion on this?

ciscoheat commented 1 week ago

@linus-amg since your validationMethod is set to onsubmit, the validation won't run until you submit the form. If you remove it to have the default auto value, it works as you would expect.

If you want to keep onsubmit but still want it to validate on deletion, deconstruct validateForm from superForm and call it in deleteItem:

const deleteItem = (index) => () => {
  $formData.tags = $formData.tags.filter((_, i) => i !== index);
  validateForm({update: true});
};
ciscoheat commented 1 week ago

@pekeler Good question, I have no opinion really but I think the default focus-input-blur is fine. If you have any further ideas about it, please let me know!

linus-amg commented 1 week ago

@linus-amg since your validationMethod is set to onsubmit, the validation won't run until you submit the form. If you remove it to have the default auto value, it works as you would expect.

If you want to keep onsubmit but still want it to validate on deletion, deconstruct validateForm from superForm and call it in deleteItem:

const deleteItem = (index) => () => {
  $formData.tags = $formData.tags.filter((_, i) => i !== index);
  validateForm({update: true});
};

@ciscoheat I've tried your recommendation of using validateForm, but I guess I was not yet able to bring across the actual issue, the problem is not that it's not validating, the problem is that it is validating, even though I'm not submitting in between deletes and adds, but still get errors for items at an index which was invalid before. I created two more recordings trying showcase the issues, one could also try it out in the SvelteLab link, I've updated the code to use "auto", but it's the same behavior.

https://github.com/user-attachments/assets/65ef90a0-df09-4cbb-a597-fe933defb3aa

https://github.com/user-attachments/assets/3c5f7104-1a45-490d-8b23-64a745a0df05

ciscoheat commented 1 week ago

I see, then you need to use update for the form data store and set it to prevent tainting, because it's the tainting that makes it validate. So your add/delete functions should be something like:

    const addItem = (evt) => {
        evt.preventDefault();
        formData.update($formData => {
            $formData.tags = [...$formData.tags, { timestamp: Date.now() }];
            return $formData;
        }, {taint: false});
    };

    const deleteItem = (index) => () => {
        formData.update($formData => {
            $formData.tags = $formData.tags.filter((_, i) => i !== index);
            //validate(`tags[${index}]`);
            return $formData;
        }, {taint: false}) 
    };
linus-amg commented 1 week ago

I see, then you need to use update for the form data store and set it to prevent tainting, because it's the tainting that makes it validate. So your add/delete functions should be something like:

  const addItem = (evt) => {
      evt.preventDefault();
      formData.update($formData => {
          $formData.tags = [...$formData.tags, { timestamp: Date.now() }];
          return $formData;
      }, {taint: false});
  };

  const deleteItem = (index) => () => {
      formData.update($formData => {
          $formData.tags = $formData.tags.filter((_, i) => i !== index);
          //validate(`tags[${index}]`);
          return $formData;
      }, {taint: false}) 
  };

Thank you very much for the suggestion @ciscoheat - will give it a try.