Skip to main content
RapidDev - Software Development Agency
v0-issues

Enabling click and submit events in v0 buttons

Buttons and form submissions that do not respond to clicks in V0 apps are almost always caused by a missing 'use client' directive. In Next.js App Router, event handlers like onClick and onSubmit only work in Client Components. Add 'use client' at the top of the file, verify the button has an onClick or type='submit' attribute, and ensure forms use the onSubmit handler on the form element rather than onClick on the submit button.

Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate7 min read5-15 minutesV0 with Next.js App Router, shadcn/ui ButtonMarch 2026RapidDev Engineering Team
TL;DR

Buttons and form submissions that do not respond to clicks in V0 apps are almost always caused by a missing 'use client' directive. In Next.js App Router, event handlers like onClick and onSubmit only work in Client Components. Add 'use client' at the top of the file, verify the button has an onClick or type='submit' attribute, and ensure forms use the onSubmit handler on the form element rather than onClick on the submit button.

Why button events fail in V0 apps

V0 generates Next.js App Router components that are Server Components by default. Server Components render HTML on the server but do not support interactive JavaScript features like event handlers, refs, or React hooks. When V0 places an onClick handler in a Server Component without adding 'use client', the button renders visually but does nothing when clicked. This is the most common interactive bug in V0 apps and affects all event-driven elements including buttons, form inputs, toggles, and dropdowns.

  • Missing 'use client' directive — onClick/onSubmit handlers are silently ignored in Server Components
  • Form using onClick on submit button instead of onSubmit on the form element
  • Button type defaulting to 'submit' inside a form, causing unexpected page refresh
  • Event handler defined but not attached — onClick={handleClick} written as onClick={handleClick()}
  • Disabled attribute or pointer-events-none CSS class preventing interaction

Error messages you might see

Error: Event handlers cannot be passed to Client Component props.

You tried to pass an onClick handler from a Server Component to a child. Either add 'use client' to the parent or move the handler to a client component.

Warning: An invalid form control with name='email' is not focusable.

A hidden or display:none form field has validation. Either remove the required attribute from hidden fields or make them visible.

Unhandled Runtime Error: TypeError: handleSubmit is not a function

The form handler is not properly defined or imported. Check the function name spelling and ensure it is defined in the same component scope.

Before you start

  • A V0 project with buttons or forms that are not responding to user interaction
  • Browser developer tools to check for console errors
  • Basic understanding of React event handling

How to fix it

1

Add 'use client' directive to the component file

Event handlers only work in Client Components. Without 'use client', Next.js treats the file as a Server Component where onClick is silently ignored.

Add 'use client' as the very first line of the file — before any imports. This is the most common fix for non-responsive buttons in V0.

Before
typescript
// Server Component — onClick is silently ignored
import { Button } from '@/components/ui/button';
export function ActionButton() {
return (
<Button onClick={() => console.log('clicked')}>Click Me</Button>
);
}
After
typescript
'use client';
import { Button } from '@/components/ui/button';
export function ActionButton() {
return (
<Button onClick={() => console.log('clicked')}>Click Me</Button>
);
}

Expected result: Clicking the button logs 'clicked' to the console. The onClick handler fires on every click.

2

Use onSubmit on the form element instead of onClick on the button

Placing the submission logic on the form's onSubmit handler ensures it fires on both button click and Enter key press. Using onClick on the submit button misses keyboard submissions.

Move the submission handler from the button's onClick to the form's onSubmit. Add event.preventDefault() to prevent the default page refresh.

Before
typescript
'use client';
export function ContactForm() {
const handleSubmit = () => {
// Submits on click but not on Enter key
console.log('submitted');
};
return (
<form>
<input name="email" type="email" />
<Button onClick={handleSubmit}>Submit</Button>
</form>
);
}
After
typescript
'use client';
import { FormEvent } from 'react';
export function ContactForm() {
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
console.log('email:', formData.get('email'));
};
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<Button type="submit">Submit</Button>
</form>
);
}

Expected result: The form submits on both button click and Enter key press. The page does not refresh.

3

Set explicit button type to prevent accidental form submission

Buttons inside a form default to type='submit'. A button meant for other actions (like opening a modal) accidentally submits the form and refreshes the page.

Add type='button' to any button inside a form that is not meant to submit it.

Before
typescript
// This button accidentally submits the form
<form onSubmit={handleSubmit}>
<Button onClick={openModal}>Add Item</Button>
<Button type="submit">Save</Button>
</form>
After
typescript
// type='button' prevents accidental form submission
<form onSubmit={handleSubmit}>
<Button type="button" onClick={openModal}>Add Item</Button>
<Button type="submit">Save</Button>
</form>

Expected result: The Add Item button opens the modal without submitting the form. Only the Save button triggers form submission.

4

Fix event handler invocation syntax

Writing onClick={handleClick()} calls the function immediately during render instead of passing it as a handler. The function runs once and its return value (usually undefined) becomes the handler.

Pass the function reference without parentheses, or wrap it in an arrow function if you need to pass arguments.

Before
typescript
// WRONG — handleClick() is called during render
<Button onClick={handleClick()}>Save</Button>
// WRONG — passes the RESULT of handleClick, not the function
<Button onClick={handleDelete(item.id)}>Delete</Button>
After
typescript
// CORRECT — passes function reference
<Button onClick={handleClick}>Save</Button>
// CORRECT — arrow function wraps the call
<Button onClick={() => handleDelete(item.id)}>Delete</Button>

Expected result: The handler fires only when the button is clicked, not during component rendering.

Complete code example

app/components/ContactForm.tsx
1'use client';
2
3import { useState, type FormEvent } from 'react';
4import { Button } from '@/components/ui/button';
5import { Input } from '@/components/ui/input';
6import { Label } from '@/components/ui/label';
7import { Textarea } from '@/components/ui/textarea';
8import { Alert, AlertDescription } from '@/components/ui/alert';
9
10export function ContactForm() {
11 const [isSubmitting, setIsSubmitting] = useState(false);
12 const [success, setSuccess] = useState(false);
13 const [error, setError] = useState<string | null>(null);
14
15 async function handleSubmit(e: FormEvent<HTMLFormElement>) {
16 e.preventDefault();
17 setIsSubmitting(true);
18 setError(null);
19
20 const formData = new FormData(e.currentTarget);
21
22 try {
23 const response = await fetch('/api/contact', {
24 method: 'POST',
25 body: JSON.stringify({
26 name: formData.get('name'),
27 email: formData.get('email'),
28 message: formData.get('message'),
29 }),
30 headers: { 'Content-Type': 'application/json' },
31 });
32
33 if (!response.ok) throw new Error('Failed to send message');
34 setSuccess(true);
35 e.currentTarget.reset();
36 } catch (err) {
37 setError(err instanceof Error ? err.message : 'Something went wrong');
38 } finally {
39 setIsSubmitting(false);
40 }
41 }
42
43 return (
44 <form onSubmit={handleSubmit} className="space-y-4 max-w-md">
45 {success && (
46 <Alert>
47 <AlertDescription>Message sent successfully!</AlertDescription>
48 </Alert>
49 )}
50 {error && (
51 <Alert variant="destructive">
52 <AlertDescription>{error}</AlertDescription>
53 </Alert>
54 )}
55 <div className="space-y-2">
56 <Label htmlFor="name">Name</Label>
57 <Input id="name" name="name" required />
58 </div>
59 <div className="space-y-2">
60 <Label htmlFor="email">Email</Label>
61 <Input id="email" name="email" type="email" required />
62 </div>
63 <div className="space-y-2">
64 <Label htmlFor="message">Message</Label>
65 <Textarea id="message" name="message" required rows={4} />
66 </div>
67 <Button type="submit" disabled={isSubmitting}>
68 {isSubmitting ? 'Sending...' : 'Send Message'}
69 </Button>
70 </form>
71 );
72}

Best practices to prevent this

  • Always add 'use client' to components with event handlers — this is the number one fix for non-responsive buttons
  • Use onSubmit on the form element, not onClick on the submit button, to handle both click and keyboard submission
  • Set type='button' on non-submit buttons inside forms to prevent accidental submission
  • Pass function references (onClick={fn}) not invocations (onClick={fn()}) as event handlers
  • Disable the submit button during async operations to prevent double submissions
  • Show loading state on the button during form submission using the disabled prop and text change
  • Always call e.preventDefault() in form onSubmit handlers to prevent page refresh
  • Provide visual feedback (success message, error alert) after form actions complete

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

My V0 Next.js app has a button with an onClick handler that does nothing when clicked. There are no errors in the console. The component does not have 'use client' at the top. What is wrong and how do I fix forms and buttons in Next.js App Router?

Frequently asked questions

Why does my onClick handler do nothing in V0?

The component is likely a Server Component (missing 'use client' directive). In Next.js App Router, event handlers are silently ignored in Server Components. Add 'use client' as the first line of the file.

Why does clicking my button refresh the entire page?

The button is inside a form element and defaults to type='submit', which triggers a page-refreshing form submission. Add e.preventDefault() in the onSubmit handler, or set the button to type='button' if it is not meant to submit the form.

How do I prevent double form submissions in V0?

Track submission state with useState. Set it to true before the async operation and false after completion. Pass disabled={isSubmitting} to the submit button to prevent additional clicks during processing.

Can I use Server Actions instead of onSubmit for forms?

Yes, Next.js Server Actions let you handle form submissions server-side without 'use client'. Use the action attribute on the form element pointing to a server action function. This is useful for simple forms that do not need client-side validation.

Why does my button work in V0 preview but not after deploying?

The V0 sandbox may handle Server/Client Component boundaries differently than Vercel production. Verify the 'use client' directive is present and that no hydration errors appear in the production browser console.

How do I handle file upload button clicks in V0?

Use a hidden input with type='file' and trigger it from a visible button using useRef. The input's onChange handler processes the selected file. Both the ref and the handler require 'use client'.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your issue.

Book a free consultation

Need help with your Lovable project?

Our experts have built 600+ apps and can solve your issue fast. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.