tjinauyeung / svelte-forms-lib

📝. A lightweight library for managing forms in Svelte
https://svelte-forms-lib-sapper-docs.now.sh/
MIT License
604 stars 59 forks source link

Support to set field/s error/s imperatively (`setFieldError`/`setErrors`) #156

Closed EstebanBorai closed 1 year ago

EstebanBorai commented 2 years ago

Set errors imperatively on certain field/s, while validating (inside the callback provided to onSubmit) a form or after creating a form.

Problem to solve

As a user I want to be able to call a function which sets an error message on certain field/s.

Intended users

Developer

User experience goal

After creating a form, setting a field's error

const { setFieldError } = createForm({ /* ... */ });

/*  Checks if the provided username is available */
async function validateUsernameServerSide(username) {
  try {
     // -- snip --
  } catch (error) {
    setFieldError('username', `The username: "${username}" is already taken!`,
  }
}

Inside the onSubmit callback

If you wanted to set a field error message from a server side validation, you could use helpers from the onSubmit function which would let you set the message for a certain field.

const form = createForm({
  onSubmit: (values, helpers) => {
    try {
     // -- snip --
    } catch (error) {
      helpers.setFieldError('username', `The username: "${username}" is already taken!`,
    }
  }
});

Proposal

Provide setFieldError for single fields and setErrors functions to the API.

Eg:

setFieldError('email', 'The email must belong to the domain "example.com"');
setErrors({
    email: 'The email must belong to the domain "example.com"',
    password: 'The password is not strong enough',
});

Further details

Setting errors outside of the validation schema when form is validated server-side or by functions that run in parallel and can't be validated by the validation schema.

Documentation

Links / references

ecker00 commented 2 years ago

This would be great! But today what is the current best approach to handling a server-side rejection like this?

EstebanBorai commented 2 years ago

This would be great! But today what is the current best approach to handling a server-side rejection like this?

I ended up writing straight to the error store.

ecker00 commented 2 years ago

I'm assuming it's something like this:

const { errors } = createForm({
  initialValues: {
    name: 'test'
  },
  onSubmit: () => {}
});

errors.set({ name: 'My custom error' });

But I'm using the <Form> component approach (not createForm), so seems I don't have access to the error store directly. Any idea on how to get access to that from <Form>?

EstebanBorai commented 2 years ago

I'm assuming it's something like this:

const { errors } = createForm({
  initialValues: {
    name: 'test'
  },
  onSubmit: () => {}
});

errors.set({ name: 'My custom error' });

But I'm using the <Form> component approach (not createForm), so seems I don't have access to the error store directly. Any idea on how to get access to that from <Form>?

Oh! Not sure how it would work for the <Form> component approach TBH.

EstebanBorai commented 2 years ago

I can see the <Form> component exposes the underlying errors store as prop, by passing it to the <slot />. Here is the code: https://github.com/tjinauyeung/svelte-forms-lib/blob/7df1b1df4f02c5ff512e2542c879056ac2d8138d/lib/components/Form.svelte#L51

You should be able to use it like:

<Form let:errors={errors}>
  <!-- Do something with the error store here -->
</Form >

Perhaps this way you can write to this?

ecker00 commented 2 years ago

Thanks for the pointers. But could not get it to work with the errors slot, when trying to call it from the onSubmit() event of the parent (Function called outside component initialization). But it worked when binding the entire Form and digging out the error store from that, bit hacky, but works.

  onSubmit: (values) => {
    // Send request and get response

    const errors = form.$$.ctx.find((a) => a && a.errors).errors; // Find the error store
    errors.set({ name: 'Manual error set' });
  }
};

let form;
<Form {...formProps} bind:this={form}>
EstebanBorai commented 1 year ago

Thanks @ecker00!