vee-validate
vee-validate copied to clipboard
useFieldParam
Is your feature request related to a problem? Please describe.
Yes, the feature request is related to the problem of synchronizing form field values with URL parameters. Currently, in vee-validate
, there is no built-in way to bind form field values to URL parameters. This can be frustrating when trying to create a form where the state needs to be preserved in the URL, such as in a filter form on a search page.
Describe the solution you'd like
The solution is a composable function useFieldParam
that provides two-way binding between URL parameters and a vee-validate
field. It uses the useField
function from vee-validate
and the useUrlSearchParams
function from @vueuse/core
to achieve this. This function takes the field name
, validation rules, and options as arguments and returns a reactive object representing the vee-validate
field, with two-way binding to the corresponding URL parameter.
Here's a rough implementation of this composable:
import { FieldContext, FieldOptions, RuleExpression, useField } from 'vee-validate';
import { useUrlSearchParams } from '@vueuse/core';
import { MaybeRef, MaybeRefOrGetter, onMounted, reactive, toValue, watch } from 'vue';
import { maybeNumberOrString } from '@/utils/numbers';
type Param = string | string[] | undefined
type ExpandedField = { init: () => void, name: string}
type UseFieldParam<TValue = unknown> = (path: MaybeRefOrGetter<string>,
rules?: MaybeRef<RuleExpression<TValue>>,
opts?: Partial<FieldOptions<TValue>>) => FieldContext<TValue> & ExpandedField;
/**
* `useFieldParam` is a composable function that provides two-way binding between URL parameters and a vee-validate field.
* It uses the `useField` function from vee-validate and the `useUrlSearchParams` function from @vueuse/core to achieve this.
*
* @param args - The arguments to pass to the `useField` function. These can include the field `name` which is what will be bound to searchParams, validation rules, and options.
* @returns FieldContext<TValue> & { init: () => void }
*/
export const useFieldParam: UseFieldParam = (...args): ReturnType<UseFieldParam<unknown>> => {
const [key, rules = [], options = {}] = args;
const initialValue = toValue(options.initialValue);
const name = toValue(key);
const searchParams = useUrlSearchParams<any>('hash');
const field = reactive(useField<Param>(
name,
(rules as any), {
...(options as any),
initialValue: searchParams[name] || initialValue
})
) as any;
/**
* `init`: Resets the field and url search param to initial value
*/
field.init = () => field.setValue(initialValue as any, true);
onMounted(() => {
if (field.value && !searchParams[name]) {
searchParams[name] = field.value;
}
});
watch(() => field.value, (val, old) => {
if (name !== undefined && val !== old) {
searchParams[name] = val;
}
});
watch(searchParams, (val) => {
if (val !== field.value) {
field.setValue(maybeNumberOrString(val[name]));
}
});
return field as any;
};
Describe alternatives you've considered
An alternative solution could be to manually watch the field values and update the URL parameters accordingly, and vice versa. However, this can lead to a lot of repetitive code if there are many fields that need to be synchronized with URL parameters. The useFieldParam
composable abstracts this logic away and makes it reusable, leading to cleaner and more maintainable code.