-
Notifications
You must be signed in to change notification settings - Fork 0
Documentation
-
Install
validierung
with your favorite package manager:npm install validierung
pnpm add validierung
yarn add validierung
-
Configure the validation through
createValidation
and pass it to the app:import { createValidation } from 'validierung' const validation = createValidation({ defaultValidationBehavior: 'lazy', validationBehavior: { change: ({ force }) => !force, lazy: ({ touched }) => touched, submit: ({ submit, hasError }) => submit || hasError } }) // With Vue 3 app.use(validation) // or 2.7 Vue.use(validation)
-
Import
useValidation
throughout the app's components.
At its core, this package exports one function, useValidation
, plus some type definitions for using TypeScript.
import { useValidation } from 'validierung'
const {
form,
submitting,
validating,
errors,
hasError,
validateFields,
resetFields,
add,
remove
} = useValidation({})
-
formData
{object}
The structure of your form data
FormData
has a structure similar to any other object you would write for v-model
data binding.
Only that, together with every property, you can provide rules to display validation errors.
Let's look at an example:
const formData = {
name: '',
password: ''
}
The above can be converted to the following:
const formData = {
name: {
$value: '',
$rules: [name => !name && 'Please enter your name']
},
password: {
$value: '',
$rules: [
password =>
password.length > 7 || 'Password has to be longer than 7 characters'
]
}
}
FormData
can contain arrays and can be deeply nested. At the leaf level,
the object should contain fields whose simplified type definition looks like the following:
type Field<T> = {
$value: T | Ref<T>
$rules?: Rule<T>[]
}
-
form
{object}
A transformed reactive form data object -
submitting
{Ref<boolean>}
True
during validation after callingvalidateFields
when there were rules returning aPromise
-
validating
{ComputedRef<boolean>}
True
while the form has any pending rules -
errors
{ComputedRef<string[]>}
All current validation error messages -
hasError
{ComputedRef<boolean>}
True
if the form has any error
Form
is a reactive object with an identical structure as the formData
input but with added properties to every field.
All objects with a $value
property are converted to an object of the following type:
type TransformedField<T> = {
$uid: number
$value: T
$errors: string[]
$hasError: boolean
$validating: boolean
$touched: boolean
$dirty: boolean
$validate(options?: ValidateOptions): Promise<void>
}
Given the data of the previous example, this would result in the structure below:
type Form = {
name: TransformedField<string>
password: TransformedField<string>
}
Listed as follows are a description of all the properties and their use case:
-
$uid
{number}
The unique id of the field. For dynamic forms, this can be used as thekey
attribute inv-for
-
$value
{any}
The current field's value. Intended to be used together withv-model
-
$errors
{string[]}
A list of validation error messages local to the field -
$hasError
{boolean}
True
while the field has any errors -
$validating
{boolean}
True
while the field has any pending rules -
$touched
{boolean}
True
if the field is touched. In most cases, this value should be set together with theblur
event. Either through$validate
or manually -
$dirty
{boolean}
True
if the$value
of the field has changed at least once -
$validate
{function}
Function to trigger validation
-
options
{object}
Options to use for validation-
setTouched
{boolean}
Set the field touched when called- Default
true
- Default
-
force
{boolean}
Validate with theforce
flag set (see validation behavior)- Default
true
- Default
-
- Returns:
{Promise<void>}
APromise
that is pending until every asynchronous rule is done validating (or resolve directly for synchronous rules)
Trigger validation across the form.
-
options
{object}
Options to use for validation-
names
{string[]}
A list of field names to validate. Names are taken from the properties in the form data- Default
undefined
Meaning validate all
- Default
-
predicate
{function}
A filter function to determine which values to keep in the resulting form data. Used likeArray.prototype.filter
but for objects- Default
() => true
Meaning keep all
- Default
-
- Returns:
{Promise}
APromise
containing the resulting form data, pending until asynchronous validation is done - Throws:
{ValidationError}
AnError
when there were any failing rules
-
predicateValue
{object}
-
key
{string}
The current key -
value
{any}
The current value -
path
{string[]}
The current path in the object
-
- Returns:
{boolean}
Returnfalse
ortrue
if the value should be kept
Reset all fields to their default value or pass an object to set specific values.
-
formData
{object}
Form data to set specific values. It has the same structure as the object passed touseValidation
- Default
undefined
Meaning use the default values
- Default
⚠️ It will not create any new fields not present in the form data initially.
Adds a new property to the form data.
-
path
{(string | number)[]}
A path where the new value is added -
value
{any}
The value to add
ℹ️ Objects with a
$value
property are transformed, as seen above.
Removes a property from the form data.
-
path
{(string | number)[]}
The path to the property begin removed
Rules are functions that should return a string when the validation fails.
They can be written purely as a function or together with a key
property in an object.
They can also return a Promise
when you have a rule that requires asynchronous code. Here is an example:
useValidation({
name: {
$value: '',
$rules: [
/**
* This is a simple rule.
* If the name doesn't exist, return the error message.
*/
name => !name && 'Please enter your name'
]
},
password: {
$value: '',
$rules: [
/**
* This is a keyed rule.
* Keyed rules will be executed together. It will receive both
* password as well as confirmPassword as arguments
* because they share the same key, 'pw'.
* If both do not match, return the error message.
*/
{
key: 'pw',
rule: (password, confirmPassword) =>
password !== confirmPassword && 'Passwords do not match'
}
]
},
confirmPassword: {
$value: '',
$rules: [
{
key: 'pw',
rule: (password, confirmPassword) =>
password !== confirmPassword && 'Passwords do not match'
}
]
}
})
ℹ️ Keyed Rules will only be called after all connected fields have been touched. This will prevent overly aggressive error messages where the user did not have time yet to interact with all the validated inputs.
General-purpose rules can be written once and then imported as needed. This will reduce duplication for common validation use cases. Take the following rules:
const rules = {
required: msg => x => !x && msg,
min: min => msg => x => x.length >= min || msg,
max: max => msg => x => x.length <= max || msg,
minMax: (min, max) => msg => x => (min <= x.length && x.length <= max) || msg,
email: msg => x => /\S+@\S+\.\S+/.test(x) || msg,
equal:
msg =>
(...xs) =>
xs.every(x => x === xs[0]) || msg
}
And then in a component:
useValidation({
field: {
$value: '',
$rules: [
rules.required('Error message'),
rules.min(1)('Error message'),
rules.max(15)('Error message'),
rules.minMax(1, 15)('Error message'),
rules.email('Error message'),
rules.equal('Error message')
]
}
})
Rules can be extended with validation behavior to customize how the validation should operate.
This is done through ValidationBehaviorFunctions
(VBF
). By default, useValidation
will try
to execute every field's rule whenever the $value
property changes. VBFs
can change this behavior.
The following diagram summarizes the validation steps:
┌──────────────────────────────────────────────┐
│ The watcher for $value has detected a change │
│ or the $validate method was called │
└──────────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Invoke the paired VBF of every rule, │
│ passing some information about how the validation was triggered │
└───────────────────────────────┬─────────────────────────────────┘
│
▼
┌─────┐ ┌──────────────────────────┐ ┌────┐
│ YES ├───────┤ Did the VBF return true? ├───────┤ NO │
└──┬──┘ └──────────────────────────┘ └──┬─┘
│ │
▼ ▼
┌─────────────────────────┐ ┌────────────┐
│ Execute the paired rule │ │ Do nothing │
└─────────────────────────┘ └────────────┘
Used to customize the validation behavior.
-
validationBehaviorInfo
{object}
-
hasError
{boolean}
True
if the paired rule of this behavior has an error -
touched
{boolean}
The touched state of the field -
dirty
{boolean}
The dirty state of the field -
force
{boolean}
True
if the validation was triggered with theforce
flag (see$validate
) -
submit
{boolean}
True
if the validation was triggered byvalidateFields
-
value
{any}
The$value
of the field
-
- Returns:
{boolean}
Returntrue
if the validation should be triggered
Combining these options gives flexibility on how rules are executed. It's common to have
shared validation behavior across different inputs. General purpose VBFs
are to be registered in createValidation
:
const validation = createValidation({
defaultValidationBehavior: 'lazier',
validationBehavior: {
change: ({ force }) => !force,
lazy: ({ touched }) => touched,
lazier: ({ force, touched, submit, hasError }) =>
force || submit || (touched && hasError),
submit: ({ submit, hasError }) => submit || hasError
}
})
app.use(validation)
Every VBF
is assigned a string
, and a default validation behavior is given. In useValidation
, they can be passed
alongside the rule:
useValidation({
field: {
$value: '',
$rules: [
// Below will use the default validation behavior
rules.required('Error message'),
// Only call the rule on submit, and afterwards if there is an error
['submit', rules.required('Error message')],
// Or pass a VBF directly instead of using a string
[info => !info.force, rules.required('Error message')]
]
}
})
You are free to name the behaviors however you like. Below is a description of the examples above:
The validation will trigger on change of $value
and after calling validateFields
.
Do not validate until the field is touched, and afterwards, be aggressive.
Like lazy
, but the validation is only aggressive until the error is resolved.
Afterward, the behavior goes back to being lazy
.
The validation is first triggered after calling validateFields
and then only if there is an error.
Rules can be extended further by adding a debounce duration.
It will delay invoking rule
until after duration
milliseconds have elapsed.
Were the rule to be called again in that time frame, only the last attempt is
considered. Take the following example:
useValidation({
name: {
$value: '',
$rules: [
[
'change', // Will be validated frequently
async name => {
// Query some API to see if the name is available
},
300 // A debounce duration of 300 ms
]
]
}
})
Debouncing a rule is useful for reducing the frequency with which it is executed. It makes sense for asynchronous rules that require access to some external resource, like an API, to reduce the number of server calls but still give frequent feedback.
⚠️ Use debounce only for asynchronous rules.
For type inference in useValidation,
make sure to define the structure of your form data
upfront and pass it as the generic parameter FormData
.
Fields have to be annotated by the Field
type:
import { useValidation, type Field } from 'validierung'
type FormData = {
field: Field<string>
}
useValidation<FormData>({
field: {
$value: '',
$rules: []
}
})
Fields can have arbitrary properties.
These are passed as the second generic argument to Field
in object form:
type FormData = {
field: Field<string, { extra: string; stuff: number }>
}
const { form } = useValidation<FormData>({
field: {
$value: '',
$rules: [],
extra: 'foo',
stuff: 42
}
})
form.field.extra // 'foo'
form.field.stuff // 42
To extend the default validation behavior, there is a special interface that needs to be declared.
Somewhere in your project, put the following in a .ts
or .d.ts
file:
declare module 'validierung' {
interface ValidationBehaviorFunctions {
change: never
lazy: never
lazier: never
submit: never
}
}
The keys of the interface need to be implemented in createValidation
:
createValidation({
defaultValidationBehavior: 'lazier',
validationBehavior: {
change: ({ force }) => !force,
lazy: ({ touched }) => touched,
lazier: ({ force, touched, submit, hasError }) =>
force || submit || (touched && hasError),
submit: ({ submit, hasError }) => submit || hasError
}
})
The parameter type of inline VBFs
can not be inferred:
type FormData = {
field: Field<string>
}
useValidation<FormData>({
field: {
$value: '',
$rules: [
[
// Annotation is necessary here
(info: ValidationBehaviorInfo) => !info.force,
() => 'Error message'
]
]
}
})