N
Nuxt3mo ago
soymilk

NuxtUI Form Submit Issue

I'm using NuxtUI and the UForm element cannot be submitted. It says that e.preventDefault is not a function: Here's a reproduction:
<template>
<UForm
:schema="z.object({ text: z.string() })"
:state="formState"
@error="(e) => console.log(e)"
@submit="(e) => console.log(e)"
>
<UInput v-model="formState.text" name="text" />
<UButton type="submit" label="Submit" />
</UForm>
</template>

<script setup lang="ts">
import * as z from "zod";

const formState = ref({ text: "" });
</script>
<template>
<UForm
:schema="z.object({ text: z.string() })"
:state="formState"
@error="(e) => console.log(e)"
@submit="(e) => console.log(e)"
>
<UInput v-model="formState.text" name="text" />
<UButton type="submit" label="Submit" />
</UForm>
</template>

<script setup lang="ts">
import * as z from "zod";

const formState = ref({ text: "" });
</script>
No description
21 Replies
kapa.ai
kapa.ai3mo ago
Hi @Wilson. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Based on the provided knowledge sources, the NuxtUI UForm component expects the @submit event handler to receive a FormSubmitEvent object, not a native DOM event. This object does not have a preventDefault method, which is why you see the error "e.preventDefault is not a function". In your code, you are using an inline arrow function for the @submit event:
@submit="(e) => console.log(e)"
@submit="(e) => console.log(e)"
This is fine, but you should not expect e to be a native event. Instead, it is a form event object containing the submitted data. You do not need to call preventDefault—NuxtUI handles form submission and validation internally. Here’s a correct example from the documentation:
<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'

const schema = z.object({
text: z.string()
})

type Schema = z.output<typeof schema>

const formState = reactive<Partial<Schema>>({
text: ''
})

function onSubmit(event: FormSubmitEvent<Schema>) {
console.log('Submitted', event.data)
}
</script>

<template>
<UForm
:schema="schema"
:state="formState"
@submit="onSubmit"
>
<UInput v-model="formState.text" name="text" />
<UButton type="submit" label="Submit" />
</UForm>
</template>
<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'

const schema = z.object({
text: z.string()
})

type Schema = z.output<typeof schema>

const formState = reactive<Partial<Schema>>({
text: ''
})

function onSubmit(event: FormSubmitEvent<Schema>) {
console.log('Submitted', event.data)
}
</script>

<template>
<UForm
:schema="schema"
:state="formState"
@submit="onSubmit"
>
<UInput v-model="formState.text" name="text" />
<UButton type="submit" label="Submit" />
</UForm>
</template>
Key points: - The @submit handler receives a custom event, not a native event. - You do not need to call preventDefault. - Use a function that handles the form data as shown above. This approach is consistent with the official NuxtUI documentation for form submission and validation handling NuxtUI Form Schema Validation. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3mo ago
<@202249480148353025> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Jericho
Jericho3mo ago
@Wilson can you try to put your UInput in a UFormField, with a "name" attribute
<UFormField name="text">
<UInput v-model="formState.text">
</UFormField>
<UFormField name="text">
<UInput v-model="formState.text">
</UFormField>
The validation of the form is using the name to display error message from the zod validation
soymilk
soymilkOP3mo ago
i tried that also. The issue is that the form event calls preventDefault somewhere in the Nuxt UI source code and it’s not a function and hence the type error. if it’s the zod fault, then it shouldn’t have worked when i submit manually via a templateRef. i think it’s probably a recent bug?
Jericho
Jericho3mo ago
the bot is explaining (not really in detail but it's true) that calling a function inline, is also calling e.preventdefault by default, but, in the @submit of the UForm component, it's not a native event The one used in UForm is a FormSubmitEvent that doesn't have the preventdefault method no it's not recent
soymilk
soymilkOP3mo ago
mb doing it inline is just to make the reproduction code as short as possible. However i don’t think declaring it as a separate function makes any difference
Jericho
Jericho3mo ago
call it without the parentheses
soymilk
soymilkOP3mo ago
that’s what i do it my code and same error
Jericho
Jericho3mo ago
@submit="yourSubmitFunction" not @submit="yourSubmitFunction()"
Dante
Dante3w ago
@kapa.ai I am also experiencing this issue of the form not submitting here is my code copiesd directly from docs:
<script setup lang="ts">
import { z } from "zod";
import type { FormSubmitEvent } from "@nuxtui/form";

const schema = z.object({
name: z.string("Name is required"),
email: z.email("Invalid email"),
password: z.string("Password is required").min(8, "Must be at least 8 characters"),
});

type Schema = z.output<typeof schema>;

const state = reactive<Partial<Schema>>({
name: undefined,
email: undefined,
password: undefined,
});
const toast = useToast();

async function handleSignUp(event: FormSubmitEvent<Schema>) {
toast.add({ title: "Success", description: "The form has been submitted.", color: "success" });
console.log(event.data);
}
</script>

<template>
<div class="grid place-items-center h-screen">
<UForm :schema="schema" :state="state" @submit="handleSignUp" class="space-y-3">
<UFormField name="name" label="Name" size="xl">
<UInput v-model="state.name" placeholder="Name" type="text" class="w-full" />
</UFormField>
<UFormField name="email" label="Email" size="xl">
<UInput v-model="state.email" placeholder="Email" type="email" class="w-full" />
</UFormField>
<UFormField name="password" label="Password" size="xl">
<UInput v-model="state.password" placeholder="Password" type="password" class="w-full" />
</UFormField>
<UButton class="flex justify-center text-lg font-semibold w-full"> Sign Up </UButton>
</UForm>

</div>
</template>
<script setup lang="ts">
import { z } from "zod";
import type { FormSubmitEvent } from "@nuxtui/form";

const schema = z.object({
name: z.string("Name is required"),
email: z.email("Invalid email"),
password: z.string("Password is required").min(8, "Must be at least 8 characters"),
});

type Schema = z.output<typeof schema>;

const state = reactive<Partial<Schema>>({
name: undefined,
email: undefined,
password: undefined,
});
const toast = useToast();

async function handleSignUp(event: FormSubmitEvent<Schema>) {
toast.add({ title: "Success", description: "The form has been submitted.", color: "success" });
console.log(event.data);
}
</script>

<template>
<div class="grid place-items-center h-screen">
<UForm :schema="schema" :state="state" @submit="handleSignUp" class="space-y-3">
<UFormField name="name" label="Name" size="xl">
<UInput v-model="state.name" placeholder="Name" type="text" class="w-full" />
</UFormField>
<UFormField name="email" label="Email" size="xl">
<UInput v-model="state.email" placeholder="Email" type="email" class="w-full" />
</UFormField>
<UFormField name="password" label="Password" size="xl">
<UInput v-model="state.password" placeholder="Password" type="password" class="w-full" />
</UFormField>
<UButton class="flex justify-center text-lg font-semibold w-full"> Sign Up </UButton>
</UForm>

</div>
</template>
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I can see a few issues in your code that might be preventing the form from submitting properly: Issues in Your Code 1. Incorrect import path: You're importing FormSubmitEvent from "@nuxtui/form", but based on the knowledge sources, it should be imported from "#ui/types" or "@nuxt/ui". 2. Incorrect Zod schema methods: You're using z.email() and passing strings directly to z.string(), but these are not valid Zod methods. The correct syntax should be: - z.string() for strings (without arguments) - .email() as a chained method for email validation - Error messages should be passed as options objects 3. Missing button type: Your UButton doesn't have type="submit" specified, which is necessary for form submission. Corrected Code Based on the patterns shown in the knowledge sources, here's the corrected version:
<script setup lang="ts">
import { z } from "zod";
import type { FormSubmitEvent } from "#ui/types";

const schema = z.object({
name: z.string({ required_error: "Name is required" }),
email: z.string({ required_error: "Email is required" }).email("Invalid email"),
password: z.string({ required_error: "Password is required" }).min(8, "Must be at least 8 characters"),
});

type Schema = z.output<typeof schema>;

const state = reactive<Partial<Schema>>({
name: undefined,
email: undefined,
password: undefined,
});

const toast = useToast();

async function handleSignUp(event: FormSubmitEvent<Schema>) {
toast.add({ title: "Success", description: "The form has been submitted.", color: "success" });
console.log(event.data);
}
</script>
<template>
<div class="grid place-items-center h-screen">
<UForm :schema="schema" :state="state" @submit="handleSignUp" class="space-y-3">
<UFormField name="name" label="Name" size="xl">
<UInput v-model="state.name" placeholder="Name" type="text" class="w-full" />
</UFormField>
<UFormField name="email" label="Email" size="xl">
<UInput v-model="state.email" placeholder="Email" type="email" class="w-full" />
</UFormField>
<UFormField name="password" label="Password" size="xl">
<UInput v-model="state.password" placeholder="Password" type="password" class="w-full" />
</UFormField>
<UButton type="submit" class="flex justify-center text-lg font-semibold w-full">
Sign Up
</UButton>
</UForm>
</div>
</template>
<script setup lang="ts">
import { z } from "zod";
import type { FormSubmitEvent } from "#ui/types";

const schema = z.object({
name: z.string({ required_error: "Name is required" }),
email: z.string({ required_error: "Email is required" }).email("Invalid email"),
password: z.string({ required_error: "Password is required" }).min(8, "Must be at least 8 characters"),
});

type Schema = z.output<typeof schema>;

const state = reactive<Partial<Schema>>({
name: undefined,
email: undefined,
password: undefined,
});

const toast = useToast();

async function handleSignUp(event: FormSubmitEvent<Schema>) {
toast.add({ title: "Success", description: "The form has been submitted.", color: "success" });
console.log(event.data);
}
</script>
<template>
<div class="grid place-items-center h-screen">
<UForm :schema="schema" :state="state" @submit="handleSignUp" class="space-y-3">
<UFormField name="name" label="Name" size="xl">
<UInput v-model="state.name" placeholder="Name" type="text" class="w-full" />
</UFormField>
<UFormField name="email" label="Email" size="xl">
<UInput v-model="state.email" placeholder="Email" type="email" class="w-full" />
</UFormField>
<UFormField name="password" label="Password" size="xl">
<UInput v-model="state.password" placeholder="Password" type="password" class="w-full" />
</UFormField>
<UButton type="submit" class="flex justify-center text-lg font-semibold w-full">
Sign Up
</UButton>
</UForm>
</div>
</template>
The key changes are: - Fixed the Zod schema syntax - Added type="submit" to the UButton - Corrected the import path for FormSubmitEvent These corrections should resolve your form submission issue. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@332238302457495564> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Dante
Dante3w ago
I see, uhm ..... @kapa.ai would this not work if i didnt have the typescript stuff?
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Based on the knowledge sources provided, I don't have specific information about whether NuxtUI forms work without TypeScript. However, I can provide some general guidance: The TypeScript types (like FormSubmitEvent<Schema> and type Schema = z.output<typeof schema>) are primarily for development-time type checking and IDE support. They don't affect runtime behavior. What you can simplify without TypeScript:
<script setup>
import { z } from "zod";

const schema = z.object({
name: z.string({ required_error: "Name is required" }),
email: z.string({ required_error: "Email is required" }).email("Invalid email"),
password: z.string({ required_error: "Password is required" }).min(8, "Must be at least 8 characters"),
});

const state = reactive({
name: undefined,
email: undefined,
password: undefined,
});

const toast = useToast();

async function handleSignUp(event) {
toast.add({ title: "Success", description: "The form has been submitted.", color: "success" });
console.log(event.data);
}
</script>

<template>
<!-- Same template as before -->
</template>
<script setup>
import { z } from "zod";

const schema = z.object({
name: z.string({ required_error: "Name is required" }),
email: z.string({ required_error: "Email is required" }).email("Invalid email"),
password: z.string({ required_error: "Password is required" }).min(8, "Must be at least 8 characters"),
});

const state = reactive({
name: undefined,
email: undefined,
password: undefined,
});

const toast = useToast();

async function handleSignUp(event) {
toast.add({ title: "Success", description: "The form has been submitted.", color: "success" });
console.log(event.data);
}
</script>

<template>
<!-- Same template as before -->
</template>
What you still need: 1. The Zod schema (this is for runtime validation, not TypeScript) 2. The @submit handler function 3. type="submit" on your button 4. Proper Zod schema syntax The form should work without the TypeScript type annotations, as long as you keep the Zod schema for validation and follow the correct event handling pattern. However, the knowledge sources don't explicitly confirm this, so I recommend testing it in your specific setup. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@332238302457495564> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Dante
Dante3w ago
@kapa.ai i spcifically mean this handleSignUp(event: FormSubmitEvent<Schema>)
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Yes, you can remove the TypeScript type annotation from the function parameter. The type annotation event: FormSubmitEvent<Schema> is purely for TypeScript's type checking and doesn't affect runtime behavior. Without TypeScript:
async function handleSignUp(event) {
toast.add({ title: "Success", description: "The form has been submitted.", color: "success" });
console.log(event.data);
}
async function handleSignUp(event) {
toast.add({ title: "Success", description: "The form has been submitted.", color: "success" });
console.log(event.data);
}
This will work exactly the same at runtime. The event parameter will still contain the form data in event.data, you just won't get TypeScript's autocomplete and type checking in your IDE. The important parts that must remain: 1. The function must accept an event parameter (even without the type) 2. You access the submitted data via event.data 3. The function is passed to @submit="handleSignUp" The TypeScript types are only for development-time assistance—they get stripped away during the build process and don't affect how the code actually runs. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@332238302457495564> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Dante
Dante3w ago
@kapa.ai how would i access the values submitted from this from, currently the console log returns this:
Proxy { <target>: {…}, <handler>: {…} }

<target>: Object { name: "dan", email: "dan@mail.com", password: "alkjfla;kjfdlkjlak" }
​​
email: "dan@mail.com"
​​
name: "dan"
​​
password: "alkjfla;kjfdlkjlak"
​​
<prototype>: Object { }

<handler>: Object { _isReadonly: false, _isShallow: false }
Proxy { <target>: {…}, <handler>: {…} }

<target>: Object { name: "dan", email: "dan@mail.com", password: "alkjfla;kjfdlkjlak" }
​​
email: "dan@mail.com"
​​
name: "dan"
​​
password: "alkjfla;kjfdlkjlak"
​​
<prototype>: Object { }

<handler>: Object { _isReadonly: false, _isShallow: false }
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Based on the knowledge sources, the event.data you're receiving is a reactive Proxy object. To access the actual values, you need to use .value or access the properties directly from the Proxy. From the knowledge sources, when working with reactive data from useFetch and similar composables in Nuxt, the data is wrapped in a ref, and you access it using .value. To access the submitted form values:
async function handleSignUp(event) {
// Access individual properties directly from event.data
console.log(event.data.name); // "dan"
console.log(event.data.email); // "dan@mail.com"
console.log(event.data.password); // "alkjfla;kjfdlkjlak"

// Or destructure the values
const { name, email, password } = event.data;
console.log(name, email, password);
}
async function handleSignUp(event) {
// Access individual properties directly from event.data
console.log(event.data.name); // "dan"
console.log(event.data.email); // "dan@mail.com"
console.log(event.data.password); // "alkjfla;kjfdlkjlak"

// Or destructure the values
const { name, email, password } = event.data;
console.log(name, email, password);
}
The Proxy object you're seeing is Vue's reactivity system at work. You don't need to unwrap it with .value in this case—you can access the properties directly from event.data as shown above. If you need a plain JavaScript object without the Proxy wrapper, you can use toRaw() from Vue:
import { toRaw } from 'vue';

async function handleSignUp(event) {
const rawData = toRaw(event.data);
console.log(rawData); // Plain object: { name: "dan", email: "dan@mail.com", password: "..." }
}
import { toRaw } from 'vue';

async function handleSignUp(event) {
const rawData = toRaw(event.data);
console.log(rawData); // Plain object: { name: "dan", email: "dan@mail.com", password: "..." }
}
This is demonstrated in the knowledge sources where toRaw() is used to convert reactive state to a normal object. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@332238302457495564> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?