11import { <% if (fileRouter) { % > createFileRoute< % } else { % > createRoute< % } %> } from '@tanstack/react-router'
22import { useForm } from '@tanstack/react-form'
3+ import type { ValidationError } from "@tanstack/react-form";
34
45<% if (codeRouter) { % >
56 import type { RootRoute } from ' @tanstack/react-router'
@@ -9,48 +10,346 @@ export const Route = createFileRoute('/demo/form')({
910})
1011< % } %>
1112
13+ type FormSchema = {
14+ fullName: string;
15+ email: string;
16+ address: {
17+ street: string;
18+ city: string;
19+ state: string;
20+ zipCode: string;
21+ country: string;
22+ };
23+ phone: string;
24+ };
25+
26+ function FieldWrapper({
27+ children,
28+ errors,
29+ label,
30+ }: {
31+ children: React.ReactNode;
32+ errors: ValidationError[];
33+ label: string;
34+ }) {
35+ return (
36+ <div >
37+ <label htmlFor ={label} className =" block font-bold mb-1 text-xl" >
38+ {label}
39+ </label >
40+ {children}
41+ {errors.length ? (
42+ <div className =" text-red-500 mt-1 font-bold" >{errors.join(", ")}</div >
43+ ) : null}
44+ </div >
45+ );
46+ }
47+
1248function FormDemo() {
13- const form = useForm({
49+ const form = useForm< FormSchema > ({
1450 defaultValues: {
15- fullName: '',
51+ fullName: "",
52+ email: "",
53+ address: {
54+ street: "",
55+ city: "",
56+ state: "",
57+ zipCode: "",
58+ country: "",
59+ },
60+ phone: "",
1661 },
1762 onSubmit: async ({ value }) => {
18- console.log(value)
63+ console.log(value);
64+ // Show success message
65+ alert("Form submitted successfully!");
1966 },
20- })
67+ });
2168
2269 return (
23- <div className =" p-4" >
24- <form
25- onSubmit ={(e) => {
26- e.preventDefault()
27- e.stopPropagation()
28- form.handleSubmit()
29- }}
30- >
31- <div >
32- <form .Field
33- name =" fullName"
34- children ={(field) => (
35- <input
36- name ={field.name}
37- value ={field.state.value}
38- onBlur ={field.handleBlur}
39- onChange ={(e) => field.handleChange(e.target.value)}
40- className="block w-full px-4 py-2 text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500"
41- />
42- )}
43- />
44- </div >
45- <button
46- type =" submit"
47- className =" mt-4 inline-flex items-center px-6 py-3 border border-transparent shadow-sm text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
70+ <div
71+ className =" flex items-center justify-center min-h-screen bg-gradient-to-br from-purple-100 to-blue-100 p-4 text-white"
72+ style ={{
73+ backgroundImage:
74+ " radial-gradient(50% 50% at 5% 40%, #f4a460 0%, #8b4513 70%, #1a0f0a 100%)" ,
75+ }}
76+ >
77+ <div className =" w-full max-w-2xl p-8 rounded-xl backdrop-blur-md bg-black/50 shadow-xl border-8 border-black/10" >
78+ <form
79+ onSubmit ={(e) => {
80+ e.preventDefault();
81+ e.stopPropagation();
82+ form.handleSubmit();
83+ }}
84+ className="space-y-6"
4885 >
49- Submit
50- </button >
51- </form >
86+ {/* Full Name Field */}
87+ <div >
88+ <form .Field
89+ name =" fullName"
90+ validators ={{
91+ onBlur: ({ value }) => {
92+ if (value.trim().length === 0) {
93+ return "Full name is required";
94+ }
95+ if (value.length < 3) {
96+ return "Name must be at least 3 characters";
97+ }
98+ return undefined;
99+ },
100+ }}
101+ children={(field) => (
102+ <FieldWrapper
103+ label =" Full Name"
104+ errors ={field.state.meta.errors}
105+ >
106+ <input
107+ id =" fullName"
108+ name =" fullName"
109+ value ={field.state.value}
110+ onBlur ={field.handleBlur}
111+ onChange ={(e) => field.handleChange(e.target.value)}
112+ className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
113+ />
114+ </FieldWrapper >
115+ )}
116+ />
117+ </div >
118+
119+ {/* Email Field */}
120+ <div >
121+ <form .Field
122+ name =" email"
123+ validators ={{
124+ onBlur: ({ value }) => {
125+ if (!value || value.trim().length === 0) {
126+ return "Email is required";
127+ }
128+ if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) {
129+ return "Invalid email address";
130+ }
131+ return undefined;
132+ },
133+ }}
134+ children={(field) => (
135+ <FieldWrapper
136+ label =" Email Address"
137+ errors ={field.state.meta.errors}
138+ >
139+ <input
140+ id =" email"
141+ name =" email"
142+ type =" email"
143+ value ={field.state.value}
144+ onBlur ={field.handleBlur}
145+ onChange ={(e) => field.handleChange(e.target.value)}
146+ className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
147+ />
148+ </FieldWrapper >
149+ )}
150+ />
151+ </div >
152+
153+ {/* Street Address */}
154+ <div >
155+ <form .Field
156+ name =" address.street"
157+ validators ={{
158+ onBlur: ({ value }) => {
159+ if (!value || value.trim().length === 0) {
160+ return "Street address is required";
161+ }
162+ return undefined;
163+ },
164+ }}
165+ children={(field) => (
166+ <FieldWrapper
167+ label =" Street Address"
168+ errors ={field.state.meta.errors}
169+ >
170+ <input
171+ id =" street"
172+ name =" street"
173+ value ={field.state.value}
174+ onBlur ={field.handleBlur}
175+ onChange ={(e) => field.handleChange(e.target.value)}
176+ className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
177+ />
178+ </FieldWrapper >
179+ )}
180+ />
181+ </div >
182+
183+ {/* City, State, Zip in a row */}
184+ <div className =" grid grid-cols-1 md:grid-cols-3 gap-4" >
185+ {/* City */}
186+ <form .Field
187+ name =" address.city"
188+ validators ={{
189+ onBlur: ({ value }) => {
190+ if (!value || value.trim().length === 0) {
191+ return "City is required";
192+ }
193+ return undefined;
194+ },
195+ }}
196+ children={(field) => (
197+ <FieldWrapper label =" City" errors ={field.state.meta.errors} >
198+ <input
199+ id =" city"
200+ name =" city"
201+ value ={field.state.value}
202+ onBlur ={field.handleBlur}
203+ onChange ={(e) => field.handleChange(e.target.value)}
204+ className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
205+ />
206+ </FieldWrapper >
207+ )}
208+ />
209+
210+ {/* State */}
211+ <form .Field
212+ name =" address.state"
213+ validators ={{
214+ onBlur: ({ value }) => {
215+ if (!value || value.trim().length === 0) {
216+ return "State is required";
217+ }
218+ return undefined;
219+ },
220+ }}
221+ children={(field) => (
222+ <FieldWrapper label =" State" errors ={field.state.meta.errors} >
223+ <input
224+ id =" state"
225+ name =" state"
226+ value ={field.state.value}
227+ onBlur ={field.handleBlur}
228+ onChange ={(e) => field.handleChange(e.target.value)}
229+ className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
230+ />
231+ </FieldWrapper >
232+ )}
233+ />
234+
235+ {/* Zip Code */}
236+ <form .Field
237+ name =" address.zipCode"
238+ validators ={{
239+ onBlur: ({ value }) => {
240+ if (!value || value.trim().length === 0) {
241+ return "Zip code is required";
242+ }
243+ if (!/^\d{5}(-\d{4})?$/.test(value)) {
244+ return "Invalid zip code format";
245+ }
246+ return undefined;
247+ },
248+ }}
249+ children={(field) => (
250+ <FieldWrapper label =" Zip Code" errors ={field.state.meta.errors} >
251+ <input
252+ id =" zipCode"
253+ name =" zipCode"
254+ value ={field.state.value}
255+ onBlur ={field.handleBlur}
256+ onChange ={(e) => field.handleChange(e.target.value)}
257+ className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
258+ />
259+ </FieldWrapper >
260+ )}
261+ />
262+ </div >
263+
264+ {/* Country */}
265+ <div >
266+ <form .Field
267+ name =" address.country"
268+ validators ={{
269+ onBlur: ({ value }) => {
270+ if (!value || value.trim().length === 0) {
271+ return "Country is required";
272+ }
273+ return undefined;
274+ },
275+ }}
276+ children={(field) => (
277+ <FieldWrapper label =" Country" errors ={field.state.meta.errors} >
278+ <select
279+ id =" country"
280+ name =" country"
281+ value ={field.state.value}
282+ onBlur ={field.handleBlur}
283+ onChange ={(e) => field.handleChange(e.target.value)}
284+ className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
285+ >
286+ <option value =" " >Select a country</option >
287+ <option value =" US" >United States</option >
288+ <option value =" CA" >Canada</option >
289+ <option value =" UK" >United Kingdom</option >
290+ <option value =" AU" >Australia</option >
291+ <option value =" DE" >Germany</option >
292+ <option value =" FR" >France</option >
293+ <option value =" JP" >Japan</option >
294+ </select >
295+ </FieldWrapper >
296+ )}
297+ />
298+ </div >
299+
300+ {/* Phone Number */}
301+ <div >
302+ <form .Field
303+ name =" phone"
304+ validators ={{
305+ onBlur: ({ value }) => {
306+ if (!value || value.trim().length === 0) {
307+ return "Phone number is required";
308+ }
309+ if (
310+ !/^(\+\d{1,3})?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/.test(
311+ value
312+ )
313+ ) {
314+ return "Invalid phone number format";
315+ }
316+ return undefined;
317+ },
318+ }}
319+ children={(field) => (
320+ <FieldWrapper
321+ label =" Phone Number"
322+ errors ={field.state.meta.errors}
323+ >
324+ <input
325+ id =" phone"
326+ name =" phone"
327+ type =" tel"
328+ value ={field.state.value}
329+ onBlur ={field.handleBlur}
330+ onChange ={(e) => field.handleChange(e.target.value)}
331+ className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
332+ placeholder="(123) 456-7890"
333+ />
334+ </FieldWrapper >
335+ )}
336+ />
337+ </div >
338+
339+ {/* Submit Button */}
340+ <div className =" flex justify-end" >
341+ <button
342+ type =" submit"
343+ disabled ={form.state.isSubmitting}
344+ className =" px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-colors disabled:opacity-50"
345+ >
346+ {form.state.isSubmitting ? "Submitting..." : "Submit"}
347+ </button >
348+ </div >
349+ </form >
350+ </div >
52351 </div >
53- )
352+ );
54353}
55354
56355<% if (codeRouter) { % >
0 commit comments