Tristan Coopman
Tristan Coopman

Reputation: 71

Inertia Dynamically add items to useForm()

I am wondering if it's possible to dynamically add items to the Inertia useForm helper.
The reason herefore is that I want to have a global form component where I can pass certain props too , so it creates the form automatically.

However it is possible to load in the form component with the custom input fields and labels , the fields are not reactive and do not update , when I look in vue dev tools it seems the useForm is reactive and contains the formData object, but it doesn't behave reactive as expected.

In /composables/formConstructor.ts

import { onBeforeMount, reactive } from "vue";
import { useForm } from "@inertiajs/vue3";

interface Fields {
    type: string;
    name: string;
}
interface Inject {
    [key: string]: null;
}

export function useFormConstructor(fields: Fields[]) {
    const formData = reactive({});
    const form = useForm({ formData });

    onBeforeMount(() => {
        fields.forEach((field) => {
            if (!field.type) {
                console.error("Error: inputs and label arguments do not match");
                throw new Error(
                    `Form constructor expects input type but got undefined instead`
                );
            }
            if (!field.name) {
                throw new Error(
                    "Form constructor expects input name but got undefined instead"
                );
            }
            formData[field.name] = null;
        });
    });

    return form;
}

In FormComponent.vue

<template>
  <div class="bg-white rounded-4 p-4 w-[70%]">
    <form
      class="mx-auto"
      @submit.prevent="form.post('https://myev-admin.test/login')"
    >
      <Label
        v-for="(field, index) in fields"
        :key="index"
        :for="field.label"
      />
      <Input
        v-for="(field, index) in fields"
        :id="field.id"
        :key="index"
        :type="field.type"
        :v-bind:value="form.formData[field.name]"
        :v-model="form.formData[field.name]"
        :placeholder="field.placeholder ? field.placeholder : ''"
        :name="field.name"
        :required="field.required"
      />
      <Button
        class="inline-block mx-auto"
        @confim="$emit('confirm')"
      >
        {{ $t(`${buttonText}`) }}<Button />
      </Button>
    </form>
  </div>
</template>

<script setup>
import Button from "./Button.vue";
import Label from "./Label.vue";
import Input from "./Input.vue";
import { useFormConstructor } from "../Composables/formConstructor";

defineEmits("confirm");
const props = defineProps({
  buttonText: {
    type: String,
    default: "confirm",
    required: true,
  },
  fields: {
    type: Array,
    required: true,
    default: () => [],
  },
  method: {
    type: String,
    default: "get",
    required: true,
  },
});

const form = useFormConstructor(props.fields);
</script>

In some component that uses the form

<FormComponent
  :fields="[
    {
      type: 'text',
      placeholder: '[email protected]',
      vModel: 'form.email',
      name: 'email',
      required: true,
      id: 'email',
      label: 'email',
    },
    {
      type: 'password',
      placeholder: 'verysafepaswword',
      vModel: 'form.password',
      name: 'password',
      required: true,
      id: 'password',
      label: 'password',
    },
  ]"
  :button-text="'login'"
  :method="'post'"
/>

I have tried to come up with numerous solutions but can't seem to find a way to make it reactive , or at least make it behave like a normal useForm().

Upvotes: 3

Views: 6068

Answers (2)

didier
didier

Reputation: 326

In my experience, dynamically fields added after useForm() are reactive, but are in fact not posted when using form.post('/somewhere')

Just found a better solution to make them posted, using the form.transform() method.

In the <template> part:
(The InputTag is some component having an error property)

<InputTag v-model="form.static_field" :error="form.error?.static_field"/>

<div v-for="index in 5" :key="index">
    <InputTag
        v-model="form[`field_${index}`]"
        :error="form.errors[`field_${index}`] ?? ''" />
</div>

In the <script setup> part:

const form = useForm({
    static_field: ''
})

function addDynamicFieldsToForm() {
    for(let x = 1; x <= 5; x++) {
        form[`field_${x}`] = `generated field ${x}`
    }
}

addDynamicFieldsToForm()

function dynamicFields() {
    const data = {}
    for(let x = 1; x <= 5; x++) {
        // Weird: why need to add these form's fields to form itself?
        data[`field_${x}`] = form[`field_${x}`]
    }
    return data
}

submit() {
    form
        .transform((data) => ({
            ...data,
            ...dynamicFields()
        }))
        .post('.poc')
}

Anyway, weird but working great: both static_field and all modified dynamic fields will be posted.

Tested with [email protected] ans @inertiajs/[email protected]

Upvotes: 4

pascal78
pascal78

Reputation: 63

I was stuck with the same problem, then I manage to use dynamic fields by assigning these fields to a key declared in useForm, it worked for me as I tried to manage settings from a mysql table passed as props:

const form = useForm({
    formData: {},
})

onBeforeMount(() => {
    props.settings.map((setting) => {
        form.formData[setting['key']] = setting['value']
    })
})

then in my template:

 <template v-for="(setting, index) in settings" :key="index">

   <FormInput v-model="form.formData[setting.key]" :id="'setting_'+index" type="text" :label="__(setting.key)" />

</template>

finally I can manage data submitted and persist the modifications in my controller via

$data = $request->input('formData');

foreach ($data as $key => $value) {
    Setting::where('key', $key)->update(['value' => $value]);
}

You can find more explanations here : Vuejs3 reactive array in a component

Upvotes: 3

Related Questions