vee-validate icon indicating copy to clipboard operation
vee-validate copied to clipboard

useFieldParam

Open 3dyuval opened this issue 9 months ago • 0 comments

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.

3dyuval avatar May 06 '24 11:05 3dyuval