Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jordanmarr/fable.formvalidation
A Fable React hook library for validating UI inputs and displaying error messages
https://github.com/jordanmarr/fable.formvalidation
Last synced: 3 months ago
JSON representation
A Fable React hook library for validating UI inputs and displaying error messages
- Host: GitHub
- URL: https://github.com/jordanmarr/fable.formvalidation
- Owner: JordanMarr
- License: mit
- Created: 2021-03-28T17:01:35.000Z (almost 4 years ago)
- Default Branch: main
- Last Pushed: 2023-12-14T18:10:41.000Z (about 1 year ago)
- Last Synced: 2024-10-11T19:43:52.717Z (3 months ago)
- Language: F#
- Size: 424 KB
- Stars: 15
- Watchers: 3
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Fable.FormValidation
A Fable React hook library for validating UI inputs and displaying error messages## Installation
Get it from NuGet![![NuGet version (Fable.FormValidation)](https://img.shields.io/nuget/v/Fable.FormValidation.svg?style=flat-square)](https://www.nuget.org/packages/Fable.FormValidation/)
## Sample Form
![validation-animated](https://user-images.githubusercontent.com/1030435/112941069-07166a80-90fc-11eb-9d24-abddaa3cd61e.gif)
## Call the useValidation() hook
``` fsharp
open Fable.FormValidation[]
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```
## Common Validation Rules
* `Rule.Required` -> Validates a textbox text value attribute at validate time.
* `Rule.MinLen n` -> Validates a textbox text value minimum length at validate time.
* `Rule.MaxLen n` -> Validates a textbox text value maximum length at validate time.
* `Rule.Regex (pattern, desc)` -> Validates a textbox text value with a regex pattern at validate time.**Example:**
``` fsharp
input [
Ref (rulesFor "First Name" [Required; MaxLen 50])
Class B.``form-control``
Value model.FName
OnChange (fun e -> setModel { model with FName = e.Value })
]
`````` fsharp
input [
Ref (rulesFor "User Email" [
Required
Regex(@"^\S+@\S+$", "Email")
])
Class B.``form-control``
Value model.Email
OnChange (fun e -> setModel { model with Email = e.Value })
]
```## Custom Validation Rules
* `Rule.CustomRule (fn)` -> Takes any function that returns a `Result`. These rules will directly validate against the current model values and will be calculated during render.**Example:**
This example features the Feliz date input with a custom rule:
``` fsharp
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 }
)
]
```## Validating Radio and Checkbox Groups
Validation rules can also be applied to non-input elements!
To validate a radio button group, you can apply validation to the parent container div:``` fsharp
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"
]
]
)
```## Creating Custom Rule Libraries
It is very easy to extract your custom rules into a reusable library.
*When creating your custom rules, you can templatize the field name with `{0}`:*``` fsharp
module CustomRules =
let mustBeTrue b = CustomRule (if b then Ok() else Error "{0} must be true")```
## Built-in Rule Functions
You can also use the existing rule functions in the `RuleFn` module in your custom rules.
In fact, some rules, like `gt`, `gte`, `lt` and `lte` exist only as as functions.
This is because the common rules like `Required` and `MinLen` all expect a textbox text value, so we would lose out of F# type safety if we tried coerce those text values into numeric values at validate time.
Fortunately, `CustomRule` allows to use these in a type-safe manner:``` fsharp
input [
Ref (rulesFor "Amount" [
CustomRule (model.Amount |> RuleFn.gte 0)
])
Class B.``form-control``
Value model.Amount
OnChange (fun e -> setModel { model with Amount = e.target?value })
]
```## Add an Error Summary
``` fsharp
Fable.FormValidation.errorSummary errors
```## Create an "error" style
When a form input is invalid, the "error" class will be appended.
You must add styling for invalid inputs in your .css file:
``` css
.error {
border: 1px solid red;
background: rgb(255, 232, 235);
}
```## Edge Cases
You may encounter a situation where your validated input field is sometimes hidden and then redisplayed (as in the case of a collapsible panel).
This can cause an issue where React regenerates a different hashcode for the input each time it is made visible.
To resolve this problem, you can add a override "vkey" (validation key) that will be used instead, which will allow Fable.FormValidation to consistently track the input.``` fsharp
input [
Ref (rulesFor "First Name" [Required; MinLen 2; MaxLen 50])
Data ("vkey", $"username-{model.Id}") // This value must uniquely identify this field
Class B.``form-control``
Value model.FName
OnChange (fun e -> setModel { model with FName = e.Value })
]
```## Overriding the `getValue` and `setStyle` functions
You may now, optionally, pass in custom `getValue` and `setStyle` implementations. Use these if you require custom logic to pull your value from a control or want to apply a different error style.```F#
let rulesFor, validate, resetValidation, errors =
FormValidation.useValidation(
setStyle = setStyleCustom,
getValue = getValueCustom
)
```_Please note that `FormValidation.useValidation` is a new static method with optional parameters. The original `useValidation` function still exists for backwards compatibility._
Here are the default implementations:
```F#
let setStyleDefault (el: Element) (fieldErrors: ValidationErrors) =
// Apply or remove error highlighting to fields
if fieldErrors.Length > 0 then
el.classList.add("error")
el.setAttribute("title", fieldErrors.[0])
else
el.classList.remove("error")
el.removeAttribute("title")let getValueDefault (el: Element) : string =
// NOTE: assumes you have opened "Fable.Core.JsInterop"
el?value
````setStyleDefault` adds or removes the "error" css class and a title/tooltip of the first error.
`getValueDefault` pulls the element's `value` property for validation.### Targetting specific controls
You can target specific input controls in your custom handlers by checking for a special class name.
For example, if you wanted to override the error highlighting to use a different class for a subset of controls, you could add mark them with a custom class name, "custom-validation":```F#
input [
Ref (rulesFor "Project Name" [ Required ])
Class "form-control custom-error" projName"
Value model.Project.Name
OnChange (fun e -> dispatch (SetProject { model.Project with Name = e.Value }))
]
```Then, you can check for that class in your `setStyle` handler:
```F#
let setStyle (el: Element) (fieldErrors: ValidationErrors) =
let errorClass =
if el.className.Contains "custom-validation"
then "custom-error"
else "error"// Apply or remove error highlighting to fields
if fieldErrors.Length > 0 then
el.classList.add(errorClass)
el.setAttribute("title", fieldErrors.[0])
else
el.classList.remove(errorClass)
el.removeAttribute("title")
```## Sample App
Click here to see the [full sample](https://github.com/JordanMarr/Fable.FormValidation/blob/main/src/SampleUI/src/UserForm.fs) app using the Fable 3 template, HookRouter and Toastify.