Lovable generates React forms with uncontrolled inputs by default, so typed values do not update the UI or trigger dependent logic. Fix this by adding useState for each field, binding value and onChange to every input, and using conditional rendering to show or hide form sections based on state. This gives you real-time previews, dynamic field visibility, and reliable form data.
Why Lovable forms do not react to user input by default
When you prompt Lovable to create a form, the AI sometimes generates inputs without proper React state bindings. In React, an input needs two things to be controlled: a value prop tied to state, and an onChange handler that updates that state. Without both, typing in the field either does nothing visible or the form data is lost when you try to submit. This becomes especially noticeable when you want dynamic behavior, like showing a Company Name field only when the user selects Business as their account type, or displaying a live character count below a text area. These features require the component to re-render whenever the input changes, which only happens with properly bound state. Another common scenario is real-time preview. If you are building a profile editor and want to show the user's name updating in a preview card as they type, the preview component needs access to the form state. Without controlled inputs, the preview stays blank or shows stale data.
- Lovable generated inputs without value and onChange bindings, leaving fields uncontrolled
- State variables were declared but never connected to the input elements
- Conditional form sections reference state that is never updated by user input
- The onChange handler updates a different state variable than the one bound to value
- Form uses defaultValue instead of value, making it uncontrolled after initial render
Error messages you might see
Warning: A component is changing an uncontrolled input to be controlledReact detected that an input started without a value prop (uncontrolled) and then received one (controlled). This happens when your initial state is undefined instead of an empty string. Initialize all form state with empty strings.
Warning: You provided a `value` prop to a form field without an `onChange` handlerThe input has a value bound to state but no onChange handler, so the user cannot type anything. The field appears frozen. Add an onChange handler that calls your setState function.
TypeError: Cannot read properties of undefined (reading 'value')Your onChange handler tries to read event.target.value but the event object is not being passed correctly. Make sure your handler receives the event parameter: onChange={(e) => setName(e.target.value)}.
Before you start
- A Lovable project with a form that needs interactive behavior
- Basic understanding of what state means in React (a value the page remembers and reacts to)
- The form should already render in the Lovable preview without errors
How to fix it
Add state variables for every form field
React needs state to track what the user types — without it, input values are lost between renders
Add state variables for every form field
React needs state to track what the user types — without it, input values are lost between renders
Open your form component in the Lovable editor. At the top of the component function, add a useState call for each input field. Each state variable should be initialized with an empty string (for text inputs), false (for checkboxes), or a sensible default. This ensures the input starts as controlled from the very first render.
const ContactForm = () => { return ( <form> <input type="text" placeholder="Your name" /> <input type="email" placeholder="Your email" /> <textarea placeholder="Your message" /> </form> );};import { useState } from "react";const ContactForm = () => { // State for each field — initialized to empty strings to keep inputs controlled const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [message, setMessage] = useState(""); return ( <form> <input type="text" placeholder="Your name" /> <input type="email" placeholder="Your email" /> <textarea placeholder="Your message" /> </form> );};Expected result: No visible change yet — the state is declared but not connected to the inputs.
Bind value and onChange to each input
Connecting state to inputs creates a two-way binding: React controls what the input displays, and user typing updates the state
Bind value and onChange to each input
Connecting state to inputs creates a two-way binding: React controls what the input displays, and user typing updates the state
For every input and textarea, add a value prop pointing to the matching state variable and an onChange handler that calls the setter. This makes each input controlled so React owns the value, and every keystroke triggers a state update that re-renders the component with the new value.
<input type="text" placeholder="Your name" /><input type="email" placeholder="Your email" /><textarea placeholder="Your message" /><input type="text" placeholder="Your name" value={name} onChange={(e) => setName(e.target.value)}/><input type="email" placeholder="Your email" value={email} onChange={(e) => setEmail(e.target.value)}/><textarea placeholder="Your message" value={message} onChange={(e) => setMessage(e.target.value)}/>Expected result: Typing in any field now updates the React state. You can verify by adding a temporary {name} display above the form.
Add conditional form sections based on user input
Dynamic field visibility makes forms shorter and less overwhelming by only showing relevant fields
Add conditional form sections based on user input
Dynamic field visibility makes forms shorter and less overwhelming by only showing relevant fields
Use conditional rendering to show or hide form sections based on the current state. For example, if you have an account type selector, show business-specific fields only when the user picks Business. Wrap the conditional section in a JavaScript expression inside your JSX. The section appears instantly when the condition becomes true because every state change triggers a re-render.
<select> <option value="personal">Personal</option> <option value="business">Business</option></select><input type="text" placeholder="Company name" /><input type="text" placeholder="Tax ID" /><select value={accountType} onChange={(e) => setAccountType(e.target.value)}> <option value="personal">Personal</option> <option value="business">Business</option></select>{/* These fields only appear when Business is selected */}{accountType === "business" && ( <div className="space-y-4"> <input type="text" placeholder="Company name" value={companyName} onChange={(e) => setCompanyName(e.target.value)} /> <input type="text" placeholder="Tax ID" value={taxId} onChange={(e) => setTaxId(e.target.value)} /> </div>)}Expected result: The Company name and Tax ID fields are hidden by default. Selecting Business from the dropdown reveals them instantly.
Add a live preview section that reflects form input in real time
Real-time preview gives users immediate visual feedback, making the form feel responsive and professional
Add a live preview section that reflects form input in real time
Real-time preview gives users immediate visual feedback, making the form feel responsive and professional
Below or beside your form, add a preview section that reads directly from the same state variables. Because React re-renders whenever state changes, the preview updates with every keystroke. This pattern is common for profile editors, email composers, and any form where users benefit from seeing their output before submitting. If the preview involves multiple components across files, this step can get complex with generated code. If you are unsure, RapidDev's engineers have solved this exact issue across 600+ projects and can handle it safely.
// No preview section exists{/* Live preview card */}<div className="rounded-lg border p-4 bg-muted/50"> <h3 className="text-sm font-medium text-muted-foreground mb-2">Preview</h3> <p className="text-lg font-semibold">{name || "Your name"}</p> <p className="text-sm text-muted-foreground">{email || "your@email.com"}</p> {message && ( <p className="mt-2 text-sm italic">"{message}"</p> )}</div>Expected result: A preview card below the form shows the name, email, and message updating live as the user types.
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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";67const ReactiveContactForm = () => {8 const [name, setName] = useState("");9 const [email, setEmail] = useState("");10 const [accountType, setAccountType] = useState("personal");11 const [companyName, setCompanyName] = useState("");12 const [message, setMessage] = useState("");1314 const handleSubmit = (e: React.FormEvent) => {15 e.preventDefault();16 console.log({ name, email, accountType, companyName, message });17 };1819 return (20 <div className="max-w-xl mx-auto space-y-6 p-6">21 <form onSubmit={handleSubmit} className="space-y-4">22 <Input placeholder="Your name" value={name} onChange={(e) => setName(e.target.value)} />23 <Input type="email" placeholder="Your email" value={email} onChange={(e) => setEmail(e.target.value)} />2425 <Select value={accountType} onValueChange={setAccountType}>26 <SelectTrigger><SelectValue placeholder="Account type" /></SelectTrigger>27 <SelectContent>28 <SelectItem value="personal">Personal</SelectItem>29 <SelectItem value="business">Business</SelectItem>30 </SelectContent>31 </Select>3233 {/* Conditional section — only visible for business accounts */}34 {accountType === "business" && (35 <Input placeholder="Company name" value={companyName} onChange={(e) => setCompanyName(e.target.value)} />36 )}3738 <Textarea placeholder="Your message" value={message} onChange={(e) => setMessage(e.target.value)} />39 <Button type="submit" className="w-full">Send Message</Button>40 </form>4142 {/* Live preview updates with every keystroke */}43 <div className="rounded-lg border p-4 bg-muted/50">44 <h3 className="text-sm font-medium text-muted-foreground mb-2">Preview</h3>45 <p className="text-lg font-semibold">{name || "Your name"}</p>46 <p className="text-sm text-muted-foreground">{email || "your@email.com"}</p>47 {accountType === "business" && companyName && (48 <p className="text-sm">{companyName}</p>49 )}50 {message && <p className="mt-2 text-sm italic">"{message}"</p>}51 </div>52 </div>53 );54};5556export default ReactiveContactForm;Best practices to prevent this
- Always initialize string state with empty strings, not undefined — this prevents the uncontrolled-to-controlled warning
- Use one useState per field for simple forms, or useReducer for forms with 10+ fields to keep the code manageable
- Bind both value and onChange on every input — missing either one breaks the controlled pattern
- Use shadcn/ui Select with onValueChange instead of onChange — it passes the value directly, not an event object
- Wrap conditional sections in a parent div with Tailwind spacing classes (space-y-4) so layout adjusts smoothly
- For real-time validation, check the state value inside the onChange handler or in a useEffect that watches the field
- Reset form state after successful submission by calling each setter with its initial value
- Keep the preview component in the same file as the form unless it is very large — this avoids prop-drilling complexity
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a React form in my Lovable project that does not update when I type. The inputs seem frozen or the values disappear on submit. Here is my form component: [paste your form component code here] Please: 1. Convert all inputs to controlled components with useState 2. Add conditional form sections that show/hide based on a dropdown selection 3. Add a live preview section that updates as I type 4. Use shadcn/ui components (Input, Select, Textarea, Button) with Tailwind CSS 5. Make sure all state is initialized with empty strings to avoid React warnings
Make this form reactive. Add useState for every input field in @src/components/ContactForm.tsx and bind value and onChange to each one. Add a conditional section: when the user selects 'Business' from the account type dropdown, show a Company Name field. Add a live preview card below the form that displays the current values as the user types. Use shadcn/ui Input, Select, Textarea, and Button components. Initialize all state with empty strings.
Frequently asked questions
How do I make Lovable form inputs update in real time?
Add a useState hook for each input field, then bind the state to the input's value prop and update it via the onChange handler. This creates a controlled component that re-renders on every keystroke, enabling real-time behavior like live previews and character counters.
Why does my Lovable form show a changing uncontrolled input to controlled warning?
This warning appears when your initial state is undefined or null instead of an empty string. React treats inputs without a value prop as uncontrolled. Initialize all form state with useState("") to ensure the input is controlled from the first render.
How do I show or hide form fields based on a dropdown selection in Lovable?
Store the dropdown value in state using useState, then use conditional rendering in your JSX. For example: {accountType === 'business' && <Input placeholder='Company name' />}. The field appears instantly when the condition is true because state changes trigger a re-render.
Can I add a live preview to my Lovable form?
Yes. Since your form fields are bound to state variables, any other part of the component can read those same variables. Add a preview section in your JSX that displays {name}, {email}, etc. It updates with every keystroke because React re-renders whenever state changes.
Should I use react-hook-form or plain useState in Lovable?
For simple forms with fewer than 10 fields, plain useState is easier to understand and works well with Lovable's code generation. For complex forms with many validation rules, ask Lovable to use react-hook-form with zod, which reduces re-renders and handles validation declaratively.
How do I reset a Lovable form after submission?
After your submit logic completes, call each setter with its initial value: setName(""), setEmail(""), and so on. If using useReducer, dispatch a reset action. This clears all fields and returns the form to its initial state.
What if I can't fix this myself?
If your form involves complex conditional logic, multi-step wizards, or state shared across multiple components, RapidDev's engineers can help. They have built reactive forms across 600+ Lovable projects and can get your form working correctly in one session.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your issue.
Book a free consultation