1
1
import { <% if (fileRouter) { % > createFileRoute< % } else { % > createRoute< % } %> } from '@tanstack/react-router'
2
2
import { useForm } from '@tanstack/react-form'
3
+ import type { ValidationError } from "@tanstack/react-form";
3
4
4
5
<% if (codeRouter) { % >
5
6
import type { RootRoute } from ' @tanstack/react-router'
@@ -9,48 +10,346 @@ export const Route = createFileRoute('/demo/form')({
9
10
})
10
11
< % } %>
11
12
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
+
12
48
function FormDemo() {
13
- const form = useForm({
49
+ const form = useForm< FormSchema > ({
14
50
defaultValues: {
15
- fullName: '',
51
+ fullName: "",
52
+ email: "",
53
+ address: {
54
+ street: "",
55
+ city: "",
56
+ state: "",
57
+ zipCode: "",
58
+ country: "",
59
+ },
60
+ phone: "",
16
61
},
17
62
onSubmit: async ({ value }) => {
18
- console.log(value)
63
+ console.log(value);
64
+ // Show success message
65
+ alert("Form submitted successfully!");
19
66
},
20
- })
67
+ });
21
68
22
69
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"
48
85
>
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 >
52
351
</div >
53
- )
352
+ );
54
353
}
55
354
56
355
<% if (codeRouter) { % >
0 commit comments