Reusable rules
createRule
To create reusable rules, it’s recommended to use createRule
. This utility simplifies defining the rule’s type, parameters, active state as well as track reactive dependencies automatically.
Example: Recreating a simple required
rule
import { createRule } from '@regle/core';
import { isFilled } from '@regle/rules';
export const required = createRule({
validator: (value: unknown) => {
return isFilled(value);
},
message: 'This field is required',
});
Available options:
validator
Type: (value, ...params?) => boolean | {$valid: boolean, [x: string]: any}
required
The validator
function determines whether the field is valid. You can write it in the same way as an inline rule.
message
Type: string | string[] | (metadata) => (string | string[])
required
This will define what error message you assign to your rule. It can be a string or a function receiving the value, params and metadata as parameters.
type
Type: string
optional
Specifies the type of validator. This is useful when multiple rules share the same target, such as required
and requiredIf
.
active
Type: boolean | (metadata) => boolean
optional
Defines the $active
state of the rule, indicating whether the rule is currently being validated. This can be computed dynamically. For more details, see Parameters and active mode.
async
Type: boolean
optional
If your validator function is not written with async await
syntax, you can enforce the rule to be async with this parameter.
tooltip
Type: string | string[] | (metadata) => (string | string[])
optional
Use tooltip
to display non-error-related messages for your field. These tooltips are aggregated and made accessible via $fields.xxx.$tooltips
. This is useful for providing additional information or guidance.
Reactive parameters
With createRule
you can easily define a rule that will depend on external reactive parameters.
Declaration
When declaring your validator, Regle will detect that your rule requires parameters and transform it into a function declaration:
export const myValidator = createRule({
validator: (value: Maybe<string>, arg: number) => {
return true;
},
message: ({ $params: [arg] }) => {
return 'This field is invalid';
}
});
The parameters detection also works with optional and spread parameters
export const myValidator = createRule({
validator: (value: Maybe<string>, optionalArg?: number, ...anyOtherArg: string[]) => {
return true;
},
message: ({ $params: [optionalArg, ...anyOtherArg] }) => {
return 'This field is invalid';
}
});
myValidator()
myValidator(5)
myValidator(5, 'foo', 'bar');
Reactivity
The real advantage of using createRule
is that it automatically registers parameters as reactive dependencies. This means your rule works seamlessly with plain values, refs, or getter functions.
const max = ref(5);
useRegle({name: ''},{
name: {
// Plain value
rule1: myValidator(5),
// Ref
rule2: myValidator(max),
// Getter value
rule3: myValidator(() => max.value)
}
})
WARNING
If you pass a raw value as a parameter, it will only be reactive if all your rules are declared as a computed or a getter function
const state = ref({name: ''})
const max = ref(5);
// ❌ Not reactive
useRegle(state, {
name: {
maxLength: maxLength(max.value),
...(max.value === 3 && {
required,
})
}
})
// ✅ Reactive
useRegle(state, () => ({
name: {
maxLength: maxLength(max.value),
...(max.value === 3 && {
required,
})
}
}))
// ✅ Reactive
const rules = computed(() => ({
name: {
maxLength: maxLength(max.value),
...(max.value === 3 && {
required,
})
}
}))
useRegle(state, rules);
Active property
The active
property option is a field that will provide the rule an on/off
behaviour.
Some rules have conditionnal validation properties, such as requiredIf
or any rule using applyIf
. This property allows you to declare the active state of the rule.
It will then be exposed in the $active
rule property and be used to reflect the validation to your user.
import { createRule, useRegle } from '@regle/core';
const myConditionalRule = createRule({
validator(value: unknown, param: string) {
// Params like `condition` will always be unwrapped here
// no need to check if it's a value, a ref or a getter function
if (param === "Yes") {
return isFilled(value);
}
return true;
},
active({ $params: [condition] }) {
return condition === 'Yes';
},
});
Usage in a component:
<script setup lang='ts'>
import { createRule, useRegle } from '@regle/core';
const yesOrNo = ref('No');
const { r$ } = useRegle({name: ''}, {
name: {
myConditionalRule: myConditionalRule(yesOrNo);
}
})
</script>
<template>
<label>
Name <span v-if="r$.$fields.name.$rules.myConditionalRule.$active">(required)</span>
</label>
</template>
Recreating requiredIf
rule
import { createRule, useRegle } from '@regle/core';
import { isFilled } from '@regle/rules';
import { ref } from 'vue';
export const requiredIf = createRule({
validator(value: unknown, condition: boolean) {
// Params like `condition` will always be unwrapped here
// no need to check if it's a value, a ref or a getter function
if (condition) {
return isFilled(value);
}
return true;
},
message({ $params: [condition] }) {
return `This field is required`,
},
active({ $params: [condition] }) {
return condition;
},
});
<script setup lang='ts'>
import { useRegle } from '@regle/core';
const condition = ref(false);
const { r$ } = useRegle({name: ''}, {
name: { required: requiredIf(condition) }
})
</script>
<template>
<div>
<input v-model="condition" type='checkbox'/>
<label>The field is required</label>
</div>
<div>
<!-- Here we can use $active to know if the rule is enabled -->
<input
v-model='form.name'
:placeholder='`Type your name${r$.$fields.name.$rules.required.$active ? "*": ""}`'
/>
<button type="button" @click="r$.$resetAll">Reset</button>
</div>
<ul v-if="r$.$errors.name.length">
<li v-for="error of r$.$errors.name" :key='error'>
{{ error }}
</li>
</ul>
</template>
Result:
Async rules
Async rules are useful for server-side validations or computationally expensive local checks. They update the $pending
state whenever invoked.
<template>
<div class="demo-container">
<div>
<input
v-model="form.email"
:class="{ pending: r$.$fields.email.$pending }"
placeholder="Type your email"
/>
<button type="button" @click="r$.$resetAll">Reset</button>
<button type="button" @click="r$.$validate">Submit</button>
</div>
<span v-if="r$.$fields.email.$pending"> Checking... </span>
<ul v-if="r$.$errors.email.length">
<li v-for="error of r$.$errors.email" :key="error">
{{ error }}
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { createRule, useRegle, type Maybe } from '@regle/core';
import { email, isEmpty } from '@regle/rules';
import { ref } from 'vue';
const checkEmailExists = createRule({
async validator(value: Maybe<string>) {
if (isEmpty(value) || !email.exec(value)) {
return true;
}
await timeout(1000);
return randomBoolean();
},
message: 'This email already exists',
});
const form = ref({ email: '' });
const { r$ } = useRegle(form, {
email: { email, checkEmailExists },
});
</script>
Metadata
Like in inline rules, you can return any data from your validator function as long as it returns at least $valid: boolean
It can be useful for returning computed data from the validator, or in async function to process api result, or api errors.
import { createRule } from '@regle/core';
export const example = createRule({
validator: (value) => {
if (value === 'regle') {
return {
$valid: false,
foo: 'bar'
}
}
return true;
},
message({foo}) {
return 'Error example';
},
});