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

Creating Reactive Input Forms with Controlled Inputs in Lovable

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.

Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate9 min read~10 minAll Lovable projects (Vite + React + TypeScript)March 2026RapidDev Engineering Team
TL;DR

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 controlled

React 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` handler

The 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

1

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.

Before
typescript
const ContactForm = () => {
return (
<form>
<input type="text" placeholder="Your name" />
<input type="email" placeholder="Your email" />
<textarea placeholder="Your message" />
</form>
);
};
After
typescript
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.

2

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.

Before
typescript
<input type="text" placeholder="Your name" />
<input type="email" placeholder="Your email" />
<textarea placeholder="Your message" />
After
typescript
<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.

3

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.

Before
typescript
<select>
<option value="personal">Personal</option>
<option value="business">Business</option>
</select>
<input type="text" placeholder="Company name" />
<input type="text" placeholder="Tax ID" />
After
typescript
<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.

4

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.

Before
typescript
// No preview section exists
After
typescript
{/* 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

src/components/ReactiveContactForm.tsx
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";
6
7const 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("");
13
14 const handleSubmit = (e: React.FormEvent) => {
15 e.preventDefault();
16 console.log({ name, email, accountType, companyName, message });
17 };
18
19 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)} />
24
25 <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>
32
33 {/* Conditional section — only visible for business accounts */}
34 {accountType === "business" && (
35 <Input placeholder="Company name" value={companyName} onChange={(e) => setCompanyName(e.target.value)} />
36 )}
37
38 <Textarea placeholder="Your message" value={message} onChange={(e) => setMessage(e.target.value)} />
39 <Button type="submit" className="w-full">Send Message</Button>
40 </form>
41
42 {/* 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};
55
56export 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.

ChatGPT Prompt

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

Lovable Prompt

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.

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.