United Kingdom: +44 (0)208 088 8978

Frontend Form Validation with Fable

In this showcase, Dragos talks about the Fable.FormValidation library and how it can help us write validation code more easily.

We're hiring Software Developers

Click here to find out more

Every time you create a form in your web application, you must consider validation to ensure that the input you receive is up to the standard required.

The validation logic can be implemented on the front end as well as in the back end. Frontend validation tends to be cheaper and faster as we use the browser client instead of having to call our backend server, which can lead to more server stress and longer waiting times.

Of course, because we rely on the client, we lose some control and a certain level of security, that's why both approaches should be considered and may be mixed together.

In this blog post, we will have a look at the Fable.FormValidation library and see how it can help us to implement frontend validation more easily and less stressfully.

Fable.FormValidation is a Fable React hook library for validation input and displaying errors.

The documentation shows us an example of how the hook is used.

open Fable.FormValidation

[<ReactComponent>]
let Page() = 
    let model, setModel = React.useState init
    let rulesFor, validate, resetValidation, errors = useValidation() 

    let save() = 
        if validate() then 
            resetValidation()
            Toastify.success "Form is valid!"

    let cancel() = 
        resetValidation()
        setModel init

You can notice we are using a [<ReactComponent>] attribute because we are using a React Hook. I point this out as I have made the mistake of not using the attribute when calling React Hooks 😉

Deconstructing the hook also provides us with four bindings which are pretty self-explanatory.

In the code avode we can see how resetValidation is used to reset the errors on the form.

rulesFor is used for defining the rules, the library provides some common constraints such as Required, MinLen and Regex.

An example provided in the documentation:

input [
    Ref (rulesFor "First Name" [Required; MaxLen 50])
    Class B.``form-control``
    Value model.FName
    OnChange (fun e -> setModel { model with FName = e.Value })
]

We can also create our own rules (constraints) using CustomRule, and we only have to give it a function that returns a Result of type <unit,string>

An example that uses Feliz instead of Fable.React:

Html.input [
        prop.ref (
            rulesFor "Birth Date" [
                Required
                CustomRule(
                    match model.BirthDate with
                    | Some bd ->
                        if bd <= DateTime.Now then
                            Ok()
                        else
                            (Error "{0} cannot be a future date")
                    | None -> Ok()
                )
            ]
        )
        prop.className "date"
        prop.type'.date
        if model.BirthDate.IsSome then
            prop.value model.BirthDate.Value
        prop.onChange (fun value ->
            let success, bd = DateTime.TryParse value

            if success then
                setModel { model with BirthDate = Some bd })
    ]

The library allows you to validate non-input elements as well, like a radio button group, as long as you apply the validation to the parent container div:

let fieldName = "Favorite .NET Language"
let rdoGroup = "FavLangGrp"

requiredField
    fieldName
    (div
        [ Class $"{B.``p-2``} {B.``form-control``}"
          Style [ Width 200 ]
          Ref(
              rulesFor
                  fieldName
                  [ CustomRule(
                        match model.FavoriteLang with
                        | None -> Error "{0} is required"
                        | Some lang when lang <> FSharp -> Error "{0} is invalid"
                        | Some lang -> Ok()
                    ) ]
          ) ]
        [ label
              [ Class B.``mr-4`` ]
              [ input
                    [ Type "radio"
                      Checked(model.FavoriteLang = Some FSharp)
                      Class B.``mr-1``
                      RadioGroup rdoGroup
                      Value "Yes"
                      OnChange(fun e ->
                          setModel
                              { model with
                                  FavoriteLang = Some FSharp }) ]
                str "F#" ]

          label
              [ Class B.``mr-4`` ]
              [ input
                    [ Type "radio"
                      Checked(model.FavoriteLang = Some CSharp)
                      Class B.``mr-1``
                      RadioGroup rdoGroup
                      Value "No"
                      OnChange(fun e ->
                          setModel
                              { model with
                                  FavoriteLang = Some CSharp }) ]
                str "C#" ]

          label
              [ Class B.``mr-4`` ]
              [ input
                    [ Type "radio"
                      Checked(model.FavoriteLang = Some VB)
                      Class B.``mr-1``
                      RadioGroup rdoGroup
                      Value "No"
                      OnChange(fun e -> setModel { model with FavoriteLang = Some VB }) ]
                str "VB" ] ])

Lastly we can show the errors in a summary

Fable.FormValidation.errorSummary errors

Summary

Fable.FormValidation can save us a lot of time and provides flexibility. It allows us to extend these features with our custom rule libraries but what I have shown you today should help get you started.

I hope this brought some awareness to this excellent library and I hope you will get to use it in your future projects 🙂