Skip to content

Commit ca8f244

Browse files
committed
fix: form.Provider
1 parent 07ddd2c commit ca8f244

File tree

3 files changed

+140
-124
lines changed

3 files changed

+140
-124
lines changed

examples/react/simple/src/index.tsx

Lines changed: 135 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from "react";
22
import ReactDOM from "react-dom/client";
3-
import { FieldApi, createFormFactory, useField } from "@tanstack/react-form";
3+
import { FieldApi, createFormFactory } from "@tanstack/react-form";
44

55
type Person = {
66
firstName: string;
@@ -41,137 +41,148 @@ export default function App() {
4141
},
4242
});
4343

44+
const [count, setCount] = React.useState(0);
45+
4446
return (
4547
<div>
48+
<button onClick={() => setCount((prev) => prev + 1)}>{count}</button>
4649
<h1>Simple Form Example</h1>
4750
{/* A pre-bound form component */}
48-
<form {...form.getFormProps()}>
49-
<div>
50-
{/* A type-safe and pre-bound field component*/}
51-
<form.Field
52-
name="firstName"
53-
validate={(value) => !value && "A first name is required"}
54-
validateAsyncOn="change"
55-
validateAsyncDebounceMs={500}
56-
validateAsync={async (value) => {
57-
await new Promise((resolve) => setTimeout(resolve, 1000));
58-
return (
59-
value.includes("error") && 'No "error" allowed in first name'
60-
);
61-
}}
62-
children={(field) => (
63-
// Avoid hasty abstractions. Render props are great!
64-
<>
65-
<input {...field.getInputProps()} />
66-
<FieldInfo field={field} />
67-
</>
68-
)}
69-
/>
70-
</div>
71-
<div>
72-
<form.Field
73-
name="lastName"
74-
children={(field) => (
75-
<>
76-
<input {...field.getInputProps()} />
77-
<FieldInfo field={field} />
78-
</>
79-
)}
80-
/>
81-
</div>
82-
<div>
83-
<form.Field
84-
name="hobbies"
85-
mode="array"
86-
children={(hobbiesField) => (
87-
<div>
88-
Hobbies
89-
<div
90-
style={{
91-
paddingLeft: "1rem",
92-
display: "flex",
93-
flexDirection: "column",
94-
gap: "1rem",
95-
}}
96-
>
97-
{!hobbiesField.state.value.length
98-
? "No hobbies found."
99-
: hobbiesField.state.value.map((_, i) => (
100-
<div
101-
key={i}
102-
style={{
103-
borderLeft: "2px solid gray",
104-
paddingLeft: ".5rem",
105-
}}
106-
>
107-
<hobbiesField.Field
108-
index={i}
109-
name="name"
110-
children={(field) => {
111-
return (
112-
<div>
113-
<label htmlFor={field.name}>Name:</label>
114-
<input
115-
name={field.name}
116-
{...field.getInputProps()}
117-
/>
118-
<button
119-
type="button"
120-
onClick={() => hobbiesField.removeValue(i)}
121-
>
122-
X
123-
</button>
124-
<FieldInfo field={field} />
125-
</div>
126-
);
127-
}}
128-
/>
129-
<hobbiesField.Field
130-
index={i}
131-
name="description"
132-
children={(field) => {
133-
return (
134-
<div>
135-
<label htmlFor={field.name}>
136-
Description:
137-
</label>
138-
<input
139-
name={field.name}
140-
{...field.getInputProps()}
141-
/>
142-
<FieldInfo field={field} />
143-
</div>
144-
);
51+
<form.Provider>
52+
<form {...form.getFormProps()}>
53+
<div>
54+
{/* A type-safe and pre-bound field component*/}
55+
<form.Field
56+
name="firstName"
57+
validateOn="change"
58+
validate={(value) => !value && "A first name is required"}
59+
validateAsyncOn="change"
60+
validateAsyncDebounceMs={500}
61+
validateAsync={async (value) => {
62+
await new Promise((resolve) => setTimeout(resolve, 1000));
63+
return (
64+
value.includes("error") && 'No "error" allowed in first name'
65+
);
66+
}}
67+
children={(field) => {
68+
// Avoid hasty abstractions. Render props are great!
69+
return (
70+
<>
71+
<input placeholder="uncontrolled" />
72+
<input {...field.getInputProps()} />
73+
<FieldInfo field={field} />
74+
</>
75+
);
76+
}}
77+
/>
78+
</div>
79+
<div>
80+
<form.Field
81+
name="lastName"
82+
children={(field) => (
83+
<>
84+
<input {...field.getInputProps()} />
85+
<FieldInfo field={field} />
86+
</>
87+
)}
88+
/>
89+
</div>
90+
<div>
91+
<form.Field
92+
name="hobbies"
93+
mode="array"
94+
children={(hobbiesField) => (
95+
<div>
96+
Hobbies
97+
<div
98+
style={{
99+
paddingLeft: "1rem",
100+
display: "flex",
101+
flexDirection: "column",
102+
gap: "1rem",
103+
}}
104+
>
105+
{!hobbiesField.state.value.length
106+
? "No hobbies found."
107+
: hobbiesField.state.value.map((_, i) => (
108+
<div
109+
key={i}
110+
style={{
111+
borderLeft: "2px solid gray",
112+
paddingLeft: ".5rem",
145113
}}
146-
/>
147-
</div>
148-
))}
114+
>
115+
<hobbiesField.Field
116+
index={i}
117+
name="name"
118+
children={(field) => {
119+
return (
120+
<div>
121+
<label htmlFor={field.name}>Name:</label>
122+
<input
123+
name={field.name}
124+
{...field.getInputProps()}
125+
/>
126+
<button
127+
type="button"
128+
onClick={() =>
129+
hobbiesField.removeValue(i)
130+
}
131+
>
132+
X
133+
</button>
134+
<FieldInfo field={field} />
135+
</div>
136+
);
137+
}}
138+
/>
139+
<hobbiesField.Field
140+
index={i}
141+
name="description"
142+
children={(field) => {
143+
return (
144+
<div>
145+
<label htmlFor={field.name}>
146+
Description:
147+
</label>
148+
<input
149+
name={field.name}
150+
{...field.getInputProps()}
151+
/>
152+
<FieldInfo field={field} />
153+
</div>
154+
);
155+
}}
156+
/>
157+
</div>
158+
))}
159+
</div>
160+
<button
161+
type="button"
162+
onClick={() =>
163+
hobbiesField.pushValue({
164+
name: "",
165+
description: "",
166+
yearsOfExperience: 0,
167+
})
168+
}
169+
>
170+
Add hobby
171+
</button>
149172
</div>
150-
<button
151-
type="button"
152-
onClick={() =>
153-
hobbiesField.pushValue({
154-
name: "",
155-
description: "",
156-
yearsOfExperience: 0,
157-
})
158-
}
159-
>
160-
Add hobby
161-
</button>
162-
</div>
173+
)}
174+
/>
175+
</div>
176+
<form.Subscribe
177+
selector={(state) => [state.canSubmit, state.isSubmitting]}
178+
children={([canSubmit, isSubmitting]) => (
179+
<button type="submit" disabled={!canSubmit}>
180+
{isSubmitting ? "..." : "Submit"}
181+
</button>
163182
)}
164183
/>
165-
</div>
166-
<form.Subscribe
167-
selector={(state) => [state.canSubmit, state.isSubmitting]}
168-
children={([canSubmit, isSubmitting]) => (
169-
<button type="submit" disabled={!canSubmit}>
170-
{isSubmitting ? "..." : "Submit"}
171-
</button>
172-
)}
173-
/>
174-
</form.Form>
184+
</form>
185+
</form.Provider>
175186
</div>
176187
);
177188
}

packages/react-form/src/useField.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export function Field<TData, TFormData>({
129129
children: (fieldApi: FieldApi<TData, TFormData>) => any
130130
} & UseFieldOptions<TData, TFormData>) {
131131
const fieldApi = useField(fieldOptions as any)
132+
132133
return (
133134
<formContext.Provider
134135
value={{ formApi: fieldApi.form, parentFieldName: fieldApi.name }}

packages/react-form/src/useForm.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ declare module '@tanstack/form-core' {
1515

1616
// eslint-disable-next-line no-shadow
1717
interface FormApi<TFormData> {
18+
Provider: (props: { children: any }) => any
1819
getFormProps: () => FormProps
1920
Field: FieldComponent<TFormData, TFormData>
2021
useField: UseField<TFormData>
@@ -40,6 +41,9 @@ export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
4041
// @ts-ignore
4142
const api = new FormApi<TData>(opts)
4243

44+
api.Provider = (props) => (
45+
<formContext.Provider {...props} value={{ formApi: api }} />
46+
)
4347
api.getFormProps = () => {
4448
return {
4549
onSubmit: formApi.handleSubmit,

0 commit comments

Comments
 (0)