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 🙂