purescript-react / purescript-lumi-components

Library of our UI components
https://lumihq.github.io/purescript-lumi-components/#/
Apache License 2.0
105 stars 5 forks source link

Improvements to when validation messages are displayed #188

Open hdgarrood opened 3 years ago

hdgarrood commented 3 years ago

At the moment, validation messages are displayed immediately, as soon as the form state for a validated form is modified:

https://user-images.githubusercontent.com/1270186/114006115-11e9a180-9858-11eb-861e-c7f81f2a4a2b.mp4

This is not ideal UX-wise - it feels a bit pushy of the form to start whining at you before you've even finished typing in the field. I think it would be preferable to wait until either the field loses focus or when the user attempts to submit the form to display these validation messages.

Another problem is that if I try to submit a form without filling in any required fields, validation errors are usually not shown. Again this is because we aren't setting validated form state fields to Modified when trying to submit the form, only when the form state for that field is changed.

These are separate issues but I think they might want tackling together; at least, we should probably ensure that trying to submit an invalid form cause any validation errors to be shown before we attempt to delay showing validation errors in form elements which have been modified until those elements lose focus, because in that case, if a "have I lost focus" flag doesn't get set properly for whatever reason then a validation error message might never appear at all.

Unfortunately there's probably no good non-breaking way of having attempted submission display validation errors - form submission is usually handled outside the form, via revalidate, which is pure and therefore doesn't provide the opportunity of modifying form state. Perhaps build could return not only a JSX, but a { trySubmit :: Effect (Maybe result), jsx :: JSX }, where trySubmit is defined as:

trySubmit = do
  props.onChange setModified
  pure (revalidate editor props props.value)

and then we might stop re-exporting revalidate from Lumi.Components.Form and make it only available from Form.Internal, and also add a warning in the docs that you should probably be using trySubmit instead.

hdgarrood commented 3 years ago

I guess this would probably also mean that we'd have to add a Mapping ModifyValidated constraint on build and also on every function which calls build but defers picking a concrete type for the form state to its own caller, which is definitely not ideal. :/

megamaddu commented 3 years ago

Since some of this is specific to build, with useForm it's usually using these fields it gives you somewhere: validated :: Maybe result and setModified :: Effect Unit. But I agree, our forms would be a lot more consistent if this was automatic in some way.

useForm could hide those two fields and instead provide a trySubmit:

trySubmit = do
  setModified
  pure validated

Ideally we would also automatically scroll back to the first invalid field or the last focused invalid field when form submission fails.

Should we also consider browser <form onSubmit> + <button type=submit> behavior? I kind of get why it's never been a priority -- we have lots of large forms and we don't want to submit them accidentally -- but I think this is a symptom of UX issues in individual fields rather than an ideal fix. For example, the select dropdown could select on tab instead of enter (a change I've been wanting to make anyway), and even display that keyboard shortcut in a little icon on the right end of the focused option. If we want to use ctrl+enter instead we could still respond to enter with a slight flash of the submit button which adds a similar keyboard hint.

hdgarrood commented 3 years ago

It would make me very happy if it were possible to use all of our forms comfortably without a mouse. I do feel like submitting on enter might not be worth the risk for larger forms, though; it's probably still quite easy to accidentally hit the enter key while filling out a form, even in a world where you can operate all of the other form elements without using it.