Skip to content
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

On submit validation failure, focus on first invalid input #243

Closed
nireno opened this issue Nov 15, 2022 · 5 comments
Closed

On submit validation failure, focus on first invalid input #243

nireno opened this issue Nov 15, 2022 · 5 comments

Comments

@nireno
Copy link
Contributor

nireno commented Nov 15, 2022

On submit validation failure, is there any feature that would help to focus on the first invalid input element?

@vmarcosp
Copy link
Member

vmarcosp commented Nov 21, 2022

You can create a hook that stores an array of tuple (or map) of (Field, React.domRef) and check for fields with errors to focus/scroll to the input.
Here it is an example of how to do something similar:

// FormWrapper.res
module Make = (Config: ReForm.Config) => {
  include ReForm.Make(Config)
  module ComparableField = Belt.Id.MakeComparable({
    type t = field
    let cmp = Pervasives.compare
  })

  type fields = Belt.MutableMap.t<ComparableField.t, Dom.element, ComparableField.identity>

  type registerFieldFn = (field, Js.Nullable.t<Dom.element>) => unit

  let useScrollToField = (~offsetTop=0, ()) => {
    let fieldsRef: React.ref<fields> = React.useRef(
      Belt.MutableMap.make(~id=module(ComparableField)),
    )

    let register = (fieldName, fieldRef) => {
      switch fieldRef->Js.Nullable.toOption {
      | Some(el) => fieldsRef.current->Belt.MutableMap.set(fieldName, el)
      | None => ()
      }
    }

    let scrollToFieldWithError = ({state}: onSubmitAPI) => {
      state.fieldsState
      ->Belt.Array.get(0)
      ->Belt.Option.map(((field, _)) => field)
      ->Belt.Option.flatMap(field => fieldsRef.current->Belt.MutableMap.get(field))
      ->Belt.Option.map(el => DomApiExtra.Window.scrollTo(0, el->DomApiExtra.offsetTop - offsetTop))
      ->ignore
    }

    (register, scrollToFieldWithError)
  }

}

// MyApp.res
module Lenses = %lenses(
 type state = {
  name: string,
 } 
)
module MyForm = FormWrapper.Make(Lenses)
@react.component
let make = () => {
   // reform stuff here (useForm, etc)
  let (registerField, scrollWhenFail) = MyForm.useScrollToField()
  
  <input ref={Field(Name)->registerField->ReactDOM.Ref.callbackDomRef}  />
}

@nireno
Copy link
Contributor Author

nireno commented Nov 22, 2022

Thanks I'll give your example a shot.
I thought that's what it would boil down to but was hoping there might be some magic to help out.

@vmarcosp
Copy link
Member

Let me know if you need help to change the example from scrolling to a focusing behaviour.

@vmarcosp
Copy link
Member

Any progress here? Can I close this issue?
Let me know if you need help.

@nireno
Copy link
Contributor Author

nireno commented Dec 11, 2022

Had some friction getting things wired up but finally got it:

  1. Wired scrollWhenFail up to onSubmitFail when configuring ReForm use.
  2. Realized order of the Validation.Schema array determines order that the fields will appear in the fieldsState array.
  3. Realized that, for onSubmitFail, the fieldsState array only contains the fields that have an error which is why it was safe to state.fieldsState->Belt.Array.get(0).
  4. Added external to allow el.focus({preventScroll: true}). Without preventScroll the focused element would just pop into view. Having it lets me follow up with el.scrollIntoView({"behavior": smooth, "block": center}) to smoothly scroll the invalid input into view.

Thanks!

@nireno nireno closed this as completed Dec 11, 2022
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

No branches or pull requests

2 participants