Form submission errors in Lovable projects are usually caused by missing preventDefault on the submit handler (which triggers a full page reload), uncontrolled form inputs (state initialized as undefined instead of empty strings), or missing error handling for the API call. Fix by adding e.preventDefault() in your onSubmit handler, initializing all form state values as empty strings, and wrapping your submission logic in a try/catch with user-facing error feedback via toast notifications.
Why form submissions fail in Lovable projects
Forms are one of the most common features in Lovable-built apps — contact forms, login forms, settings pages, checkout flows. When a form submission fails, it can be frustrating because the UI often provides no feedback about what went wrong. The most basic failure is a missing preventDefault call. In React, form elements trigger the browser's default submit behavior (a full page reload) unless you explicitly prevent it. If your onSubmit handler does not call e.preventDefault(), the page reloads before your JavaScript can process the form data, and any API call is aborted. Another common issue is uncontrolled inputs. React expects form inputs to be either controlled (value comes from state) or uncontrolled (value managed by the DOM). Lovable's AI sometimes generates controlled inputs but initializes the state as undefined instead of an empty string. This causes React to warn about switching from uncontrolled to controlled inputs, and can cause the form to submit with undefined values instead of the actual input content. Finally, many form submission failures happen silently because there is no error handling. The API call fails (network error, validation error, server error), but the UI does not tell the user anything — the button just stops loading.
- Missing e.preventDefault() in the submit handler — browser reloads the page and aborts the submission
- Form state initialized as undefined instead of empty strings — inputs behave as uncontrolled
- No error handling on the API call — submission fails silently with no user feedback
- Validation running on submit but not preventing the API call when validation fails
- Loading state not managed — users click submit multiple times, creating duplicate submissions
Error messages you might see
A component is changing an uncontrolled input to be controlledA form input started with an undefined value (uncontrolled) and then received a string value from state (controlled). Initialize all form state values as empty strings: useState('') instead of useState() or useState(undefined).
TypeError: Cannot read properties of undefined (reading 'trim')Your code tries to call .trim() on a form value that is undefined. This happens when form state is not initialized. Set default values for all form fields in your useState or form library initialization.
Form submission canceled because the form is not connectedThe form element was removed from the DOM during submission (likely due to a page reload or component unmount). Add e.preventDefault() to your submit handler to prevent the default browser form action.
Before you start
- A Lovable project with at least one form component that users fill out and submit
- Understanding that forms in React need explicit submit handling (they do not submit like traditional HTML forms)
- Access to the browser console to check for error messages when submission fails
How to fix it
Add preventDefault to your form submit handler
Without it, the browser performs a full page reload that aborts your JavaScript form processing
Add preventDefault to your form submit handler
Without it, the browser performs a full page reload that aborts your JavaScript form processing
Find your form component and check the onSubmit handler. The very first line should call e.preventDefault() to stop the browser from reloading the page. This lets your React code handle the submission with JavaScript. Also add the type="submit" attribute to your submit button so it triggers the form's onSubmit event rather than needing a separate onClick handler.
// Form reloads the page on submit — data never reaches the APIconst handleSubmit = async () => { await supabase.from("contacts").insert({ name, email, message });};return ( <form> {/* inputs */} <button onClick={handleSubmit}>Send</button> </form>);// Form handles submission correctly without page reloadconst handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); // Prevent browser default form action await supabase.from("contacts").insert({ name, email, message });};return ( <form onSubmit={handleSubmit}> {/* inputs */} <Button type="submit">Send</Button> </form>);Expected result: The form submits without a page reload. The browser stays on the same page and your API call executes.
Initialize form state with empty strings, not undefined
React controlled inputs require a defined initial value to avoid switching between controlled and uncontrolled modes
Initialize form state with empty strings, not undefined
React controlled inputs require a defined initial value to avoid switching between controlled and uncontrolled modes
Check your form state declarations. Every useState that controls a form input should be initialized with an empty string, not left undefined. For number inputs, use an empty string and convert to a number on submit. For checkbox inputs, use false. This prevents the React warning about controlled vs uncontrolled inputs and ensures form values are always strings that can be safely used with .trim() or .length checks.
// State starts as undefined — causes 'uncontrolled to controlled' warningconst [name, setName] = useState();const [email, setEmail] = useState();const [message, setMessage] = useState();// Later: name.trim() throws TypeError because name is undefined// State starts as empty strings — inputs are always controlledconst [name, setName] = useState("");const [email, setEmail] = useState("");const [message, setMessage] = useState("");// Safe to use: name.trim() returns "" on empty input, no errorExpected result: No React warnings about controlled/uncontrolled inputs. Form values are always strings that can be safely validated and submitted.
Add loading state and error feedback with toast notifications
Without feedback, users do not know if the form is submitting, succeeded, or failed
Add loading state and error feedback with toast notifications
Without feedback, users do not know if the form is submitting, succeeded, or failed
Add a loading state that disables the submit button during submission and shows a spinner. Wrap your API call in a try/catch block. On success, show a success toast and optionally reset the form. On failure, show an error toast with a user-friendly message. This prevents duplicate submissions and gives users clear feedback. If building robust form validation and error handling feels complex with generated code, RapidDev's engineers have built production-ready forms across 600+ Lovable projects.
// No loading state, no error handling — silent failureconst handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); await supabase.from("contacts").insert({ name, email, message });};// Full loading state and error handling with toast feedbackconst [isSubmitting, setIsSubmitting] = useState(false);const { toast } = useToast();const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsSubmitting(true); try { const { error } = await supabase .from("contacts") .insert({ name, email, message }); if (error) throw error; toast({ title: "Message sent", description: "We'll get back to you soon." }); setName(""); setEmail(""); setMessage(""); } catch (err) { toast({ title: "Failed to send", description: "Please try again or contact support.", variant: "destructive", }); } finally { setIsSubmitting(false); }};Expected result: The submit button shows a loading state during submission. Success or failure messages appear as toast notifications. The form resets on success.
Add basic form validation before submission
Submitting empty or invalid data wastes API calls and creates bad records in your database
Add basic form validation before submission
Submitting empty or invalid data wastes API calls and creates bad records in your database
Before calling your API, validate that required fields are filled in and email addresses have a valid format. Show validation errors inline below the relevant input fields. Only proceed with the API call if all validations pass. Keep validation simple — check for empty strings and basic format patterns rather than complex regex.
// Submits even with empty fields — creates empty recordsconst handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); await supabase.from("contacts").insert({ name, email, message });};// Validates before submitting — prevents empty recordsconst [errors, setErrors] = useState<Record<string, string>>({});const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); const newErrors: Record<string, string> = {}; if (!name.trim()) newErrors.name = "Name is required"; if (!email.trim()) newErrors.email = "Email is required"; else if (!email.includes("@")) newErrors.email = "Enter a valid email"; if (!message.trim()) newErrors.message = "Message is required"; setErrors(newErrors); if (Object.keys(newErrors).length > 0) return; // Stop if errors // Proceed with API call only if validation passes setIsSubmitting(true); // ... rest of submit logic};Expected result: Empty or invalid fields show error messages below the inputs. The form only submits to the API when all validations pass.
Complete code example
1import { useState } from "react";2import { Button } from "@/components/ui/button";3import { Input } from "@/components/ui/input";4import { Textarea } from "@/components/ui/textarea";5import { Label } from "@/components/ui/label";6import { useToast } from "@/hooks/use-toast";7import { supabase } from "@/integrations/supabase/client";8import { Loader2 } from "lucide-react";910const ContactForm = () => {11 const [name, setName] = useState("");12 const [email, setEmail] = useState("");13 const [message, setMessage] = useState("");14 const [isSubmitting, setIsSubmitting] = useState(false);15 const [errors, setErrors] = useState<Record<string, string>>({});16 const { toast } = useToast();1718 const handleSubmit = async (e: React.FormEvent) => {19 e.preventDefault();20 const newErrors: Record<string, string> = {};21 if (!name.trim()) newErrors.name = "Name is required";22 if (!email.includes("@")) newErrors.email = "Valid email is required";23 if (!message.trim()) newErrors.message = "Message is required";24 setErrors(newErrors);25 if (Object.keys(newErrors).length > 0) return;2627 setIsSubmitting(true);28 try {29 const { error } = await supabase30 .from("contacts")31 .insert({ name: name.trim(), email: email.trim(), message: message.trim() });32 if (error) throw error;33 toast({ title: "Sent!", description: "We will reply soon." });34 setName(""); setEmail(""); setMessage("");35 } catch {36 toast({ title: "Error", description: "Please try again.", variant: "destructive" });37 } finally {38 setIsSubmitting(false);39 }40 };4142 return (43 <form onSubmit={handleSubmit} className="space-y-4 max-w-md">44 <div>45 <Label htmlFor="name">Name</Label>46 <Input id="name" value={name} onChange={(e) => setName(e.target.value)} />47 {errors.name && <p className="text-sm text-destructive mt-1">{errors.name}</p>}48 </div>49 <div>50 <Label htmlFor="email">Email</Label>51 <Input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />52 {errors.email && <p className="text-sm text-destructive mt-1">{errors.email}</p>}53 </div>54 <div>55 <Label htmlFor="message">Message</Label>56 <Textarea id="message" value={message} onChange={(e) => setMessage(e.target.value)} />57 {errors.message && <p className="text-sm text-destructive mt-1">{errors.message}</p>}58 </div>59 <Button type="submit" disabled={isSubmitting}>60 {isSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}61 {isSubmitting ? "Sending..." : "Send Message"}62 </Button>63 </form>64 );65};6667export default ContactForm;Best practices to prevent this
- Always call e.preventDefault() as the first line in your form submit handler to prevent page reloads
- Initialize all form state with empty strings (not undefined) to keep inputs controlled from the start
- Add a loading state (isSubmitting) that disables the submit button to prevent duplicate submissions
- Use try/catch around API calls and show toast notifications for both success and failure
- Validate required fields before calling the API — prevent empty submissions at the form level
- Show validation errors inline below the relevant input field, not in a generic alert at the top
- Reset form fields after successful submission so users know the form is ready for a new entry
- Use the Button component from shadcn/ui with type="submit" rather than a plain button with onClick
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
My Lovable (lovable.dev) project has a form that fails silently when submitted. The project uses React + TypeScript + Supabase + shadcn/ui. Here is my form component code: [paste your form component here] The form fields are: [list your fields] It should submit to: [Supabase table name or API endpoint] Please: 1. Add e.preventDefault() if missing 2. Fix any uncontrolled input issues (initialize state with empty strings) 3. Add loading state with a disabled submit button during submission 4. Add try/catch error handling with toast notifications 5. Add basic validation for required fields
My contact form at @src/components/ContactForm.tsx is not working. When I click submit, nothing happens and no data appears in the Supabase table. Please fix the form to: add e.preventDefault() to the submit handler, initialize all form state with empty strings, add a loading state that disables the button during submission, wrap the Supabase insert in try/catch with toast notifications for success and failure, and add validation for required fields.
Frequently asked questions
Why does my Lovable form reload the page when I click submit?
Your submit handler is missing e.preventDefault(). Without it, the browser performs its default form action (a full page reload). Add e.preventDefault() as the first line of your onSubmit function.
What does 'changing an uncontrolled input to be controlled' mean?
It means your form input started without a value (undefined) and then received a value from React state. Fix this by initializing your useState with an empty string: useState('') instead of useState() or useState(undefined).
How do I show a loading spinner on the submit button?
Add an isSubmitting state. Set it to true before your API call and false in a finally block. Use it to conditionally render a Loader2 icon from lucide-react and disable the button: disabled={isSubmitting}.
How do I show success or error messages after form submission?
Use the useToast hook from shadcn/ui. Call toast({ title: 'Success', description: 'Your message' }) on success and toast({ title: 'Error', variant: 'destructive' }) in the catch block. Toasts appear as non-intrusive notifications.
How do I prevent users from submitting the same form twice?
Disable the submit button while the form is submitting by setting disabled={isSubmitting} on the Button component. This prevents double-clicks and duplicate API calls. Re-enable the button in a finally block.
Should I validate on the client side, server side, or both?
Both. Client-side validation (in your React code) provides instant feedback and prevents unnecessary API calls. Server-side validation (in Supabase RLS policies or Edge Functions) protects against tampered requests. Always validate on the server even if you validate on the client.
What if I can't fix form issues myself?
Form handling that involves multi-step flows, file uploads, payment processing, or complex validation logic can be challenging with generated code. RapidDev's engineers have built production-ready forms across 600+ Lovable projects and can ensure your forms work reliably.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your issue.
Book a free consultation