Best way to set reactive form data coming from a list in Vue3 with Composition API?

Viewed 917

In Vue3 (Composition API) I have a form-component that needs to be filled with data, coming from a datalist-component. In the form-component do the following:

<input
  v-model="formdata.formfields.FirstName"
  type="text"
/>
<input
  v-model="formdata.formfields.LastName"
  type="text"
/>
  ...
  props: {
    fields: {
      type: Object,
      default: () => ({}), 
    },
  },
  setup(props, context) {
    const formdata = reactive({
      formfields: props.fields,
    });
    
    watch(
      () => props.fields,
      (current, previous) => {
        formdata.formfields= current;
      },
      { deep: true, immediate: true }
    );

Since it's not allowed to put props directly into the v-model, I introduced the formdata variable.

Then in the datalist-component I pass the fields to this form-component like this, with fields coming from an entry in the datalist:

    <dataform :fields="listitemfields" />

This works, but I find it a bit ugly. Instead of doing formdata.formfields.FirstName I would rather do this with formfields.FirstName only. But I can't seem to get formfields reactive in that way then.

The following doesn NOT work:

const formfields = reactive({});

watch(
  () => props.fields,
  (current, previous) => {
    formfields= current;
  },
  { deep: true, immediate: true }
);

Any ideas?

Is this even a good way to pass data like this to a form-component? I've looked into emit etc., but it seems even more complicated that way.

1 Answers

The problem is, formfields still holds a reference to the prop and mutates it.

This is commonly done by implementing two-way binding, i.e. a component accepts modelValue prop as input and emits update:modelValue as output. Prop and event name could be different but this way they conform v-model:

const formfields = reactive({});

watch(
  () => props.modelValue,
  (current) => {
    if (!deepEqual(formfields, current))
      Object.assign(formfields, current)
  },
  { deep: true, immediate: true }
);

watch(
  formfields,
  (current, previous) => {
    if (!deepEqual(current, previous))
      emit('update:modelValue', current)
  },
  { deep: true }
);

It depends on how the component is supposed to work. If input data is received only on mount and not supposed to be updated then the first watch could be replaced with Object.assign(formfields, props.modelValue).

Related