Reputation: 71
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
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
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