Skip to content

Commit 0d35c5b

Browse files
committed
fix: updating form example to 1.0
1 parent 41ee50b commit 0d35c5b

File tree

8 files changed

+432
-364
lines changed

8 files changed

+432
-364
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { useStore } from '@tanstack/react-form'
2+
import { useFieldContext, useFormContext } from '../hooks/demo.form-context'
3+
4+
export function SubscribeButton({ label }: { label: string }) {
5+
const form = useFormContext()
6+
return (
7+
<form.Subscribe selector={(state) => state.isSubmitting}>
8+
{(isSubmitting) => (
9+
<button
10+
disabled={isSubmitting}
11+
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"
12+
>
13+
{label}
14+
</button>
15+
)}
16+
</form.Subscribe>
17+
)
18+
}
19+
20+
export function TextField({
21+
label,
22+
placeholder,
23+
}: {
24+
label: string
25+
placeholder?: string
26+
}) {
27+
const field = useFieldContext<string>()
28+
const errors = useStore(field.store, (state) => state.meta.errors)
29+
30+
return (
31+
<div>
32+
<label htmlFor={label} className="block font-bold mb-1 text-xl">
33+
{label}
34+
<input
35+
value={field.state.value}
36+
placeholder={placeholder}
37+
onBlur={field.handleBlur}
38+
onChange={(e) => field.handleChange(e.target.value)}
39+
className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
40+
/>
41+
</label>
42+
{errors.map((error: string) => (
43+
<div key={error} className="text-red-500 mt-1 font-bold">
44+
{error}
45+
</div>
46+
))}
47+
</div>
48+
)
49+
}
50+
51+
export function TextArea({
52+
label,
53+
rows = 3,
54+
}: {
55+
label: string
56+
rows?: number
57+
}) {
58+
const field = useFieldContext<string>()
59+
const errors = useStore(field.store, (state) => state.meta.errors)
60+
61+
return (
62+
<div>
63+
<label htmlFor={label} className="block font-bold mb-1 text-xl">
64+
{label}
65+
<textarea
66+
value={field.state.value}
67+
onBlur={field.handleBlur}
68+
rows={rows}
69+
onChange={(e) => field.handleChange(e.target.value)}
70+
className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
71+
/>
72+
</label>
73+
{errors.map((error: string) => (
74+
<div key={error} className="text-red-500 mt-1 font-bold">
75+
{error}
76+
</div>
77+
))}
78+
</div>
79+
)
80+
}
81+
82+
export function Select({
83+
label,
84+
children,
85+
}: {
86+
label: string
87+
children: React.ReactNode
88+
}) {
89+
const field = useFieldContext<string>()
90+
const errors = useStore(field.store, (state) => state.meta.errors)
91+
92+
return (
93+
<div>
94+
<label htmlFor={label} className="block font-bold mb-1 text-xl">
95+
{label}
96+
</label>
97+
<select
98+
name={field.name}
99+
value={field.state.value}
100+
onBlur={field.handleBlur}
101+
onChange={(e) => field.handleChange(e.target.value)}
102+
className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
103+
>
104+
{children}
105+
</select>
106+
{errors.map((error: string) => (
107+
<div key={error} className="text-red-500 mt-1 font-bold">
108+
{error}
109+
</div>
110+
))}
111+
</div>
112+
)
113+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { createFormHookContexts } from '@tanstack/react-form'
2+
3+
export const { fieldContext, useFieldContext, formContext, useFormContext } =
4+
createFormHookContexts()
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { createFormHook } from '@tanstack/react-form'
2+
3+
import {
4+
Select,
5+
SubscribeButton,
6+
TextArea,
7+
TextField,
8+
} from '../components/demo.FormComponents'
9+
import { fieldContext, formContext } from './demo.form-context'
10+
11+
export const { useAppForm } = createFormHook({
12+
fieldComponents: {
13+
TextField,
14+
Select,
15+
TextArea,
16+
},
17+
formComponents: {
18+
SubscribeButton,
19+
},
20+
fieldContext,
21+
formContext,
22+
})
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import { <% if (fileRouter) { %>createFileRoute<% } else { %>createRoute<% } %> } from '@tanstack/react-router'
2+
3+
import { useAppForm } from '../hooks/demo.form'
4+
5+
<% if (codeRouter) { %>
6+
import type { RootRoute } from '@tanstack/react-router'
7+
<% } else { %>
8+
export const Route = createFileRoute('/demo/form')({
9+
component: AddressForm,
10+
})
11+
<% } %>
12+
13+
function AddressForm() {
14+
const form = useAppForm({
15+
defaultValues: {
16+
fullName: '',
17+
email: '',
18+
address: {
19+
street: '',
20+
city: '',
21+
state: '',
22+
zipCode: '',
23+
country: '',
24+
},
25+
phone: '',
26+
},
27+
validators: {
28+
onBlur: ({ value }) => {
29+
const errors = {
30+
fields: {},
31+
} as {
32+
fields: Record<string, string>
33+
}
34+
if (value.fullName.trim().length === 0) {
35+
errors.fields.fullName = 'Full name is required'
36+
}
37+
return errors
38+
},
39+
},
40+
onSubmit: ({ value }) => {
41+
console.log(value)
42+
// Show success message
43+
alert('Form submitted successfully!')
44+
},
45+
})
46+
47+
return (
48+
<div
49+
className="flex items-center justify-center min-h-screen bg-gradient-to-br from-purple-100 to-blue-100 p-4 text-white"
50+
style={{
51+
backgroundImage:
52+
'radial-gradient(50% 50% at 5% 40%, #f4a460 0%, #8b4513 70%, #1a0f0a 100%)',
53+
}}
54+
>
55+
<div className="w-full max-w-2xl p-8 rounded-xl backdrop-blur-md bg-black/50 shadow-xl border-8 border-black/10">
56+
<form
57+
onSubmit={(e) => {
58+
e.preventDefault()
59+
e.stopPropagation()
60+
form.handleSubmit()
61+
}}
62+
className="space-y-6"
63+
>
64+
<form.AppField
65+
name="fullName"
66+
children={(field) => <field.TextField label="Full Name" />}
67+
/>
68+
69+
<form.AppField
70+
name="email"
71+
validators={{
72+
onBlur: ({ value }) => {
73+
if (!value || value.trim().length === 0) {
74+
return 'Email is required'
75+
}
76+
if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) {
77+
return 'Invalid email address'
78+
}
79+
return undefined
80+
},
81+
}}
82+
children={(field) => <field.TextField label="Email" />}
83+
/>
84+
85+
<form.AppField
86+
name="address.street"
87+
validators={{
88+
onBlur: ({ value }) => {
89+
if (!value || value.trim().length === 0) {
90+
return 'Street address is required'
91+
}
92+
return undefined
93+
},
94+
}}
95+
children={(field) => <field.TextField label="Street Address" />}
96+
/>
97+
98+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
99+
<form.AppField
100+
name="address.city"
101+
validators={{
102+
onBlur: ({ value }) => {
103+
if (!value || value.trim().length === 0) {
104+
return 'City is required'
105+
}
106+
return undefined
107+
},
108+
}}
109+
children={(field) => <field.TextField label="City" />}
110+
/>
111+
<form.AppField
112+
name="address.state"
113+
validators={{
114+
onBlur: ({ value }) => {
115+
if (!value || value.trim().length === 0) {
116+
return 'State is required'
117+
}
118+
return undefined
119+
},
120+
}}
121+
children={(field) => <field.TextField label="State" />}
122+
/>
123+
<form.AppField
124+
name="address.zipCode"
125+
validators={{
126+
onBlur: ({ value }) => {
127+
if (!value || value.trim().length === 0) {
128+
return 'Zip code is required'
129+
}
130+
if (!/^\d{5}(-\d{4})?$/.test(value)) {
131+
return 'Invalid zip code format'
132+
}
133+
return undefined
134+
},
135+
}}
136+
children={(field) => <field.TextField label="Zip Code" />}
137+
/>
138+
</div>
139+
140+
<form.AppField
141+
name="address.country"
142+
validators={{
143+
onBlur: ({ value }) => {
144+
if (!value || value.trim().length === 0) {
145+
return 'Country is required'
146+
}
147+
return undefined
148+
},
149+
}}
150+
children={(field) => (
151+
<field.Select label="Country">
152+
<option value="">Select a country</option>
153+
<option value="US">United States</option>
154+
<option value="CA">Canada</option>
155+
<option value="UK">United Kingdom</option>
156+
<option value="AU">Australia</option>
157+
<option value="DE">Germany</option>
158+
<option value="FR">France</option>
159+
<option value="JP">Japan</option>
160+
</field.Select>
161+
)}
162+
/>
163+
164+
<form.AppField
165+
name="phone"
166+
validators={{
167+
onBlur: ({ value }) => {
168+
if (!value || value.trim().length === 0) {
169+
return 'Phone number is required'
170+
}
171+
if (
172+
!/^(\+\d{1,3})?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/.test(
173+
value,
174+
)
175+
) {
176+
return 'Invalid phone number format'
177+
}
178+
return undefined
179+
},
180+
}}
181+
children={(field) => (
182+
<field.TextField label="Phone" placeholder="123-456-7890" />
183+
)}
184+
/>
185+
186+
<div className="flex justify-end">
187+
<form.AppForm>
188+
<form.SubscribeButton label="Submit" />
189+
</form.AppForm>
190+
</div>
191+
</form>
192+
</div>
193+
</div>
194+
)
195+
}
196+
197+
<% if (codeRouter) { %>
198+
export default (parentRoute: RootRoute) => createRoute({
199+
path: '/demo/form',
200+
component: Addres,
201+
getParentRoute: () => parentRoute,
202+
})
203+
<% } %>

0 commit comments

Comments
 (0)