fix: form.Provider

This commit is contained in:
Tanner Linsley
2023-05-04 16:05:36 -07:00
parent 07ddd2c2d6
commit ca8f244b5e
3 changed files with 140 additions and 124 deletions

View File

@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import { FieldApi, createFormFactory, useField } from "@tanstack/react-form"; import { FieldApi, createFormFactory } from "@tanstack/react-form";
type Person = { type Person = {
firstName: string; firstName: string;
@@ -41,137 +41,148 @@ export default function App() {
}, },
}); });
const [count, setCount] = React.useState(0);
return ( return (
<div> <div>
<button onClick={() => setCount((prev) => prev + 1)}>{count}</button>
<h1>Simple Form Example</h1> <h1>Simple Form Example</h1>
{/* A pre-bound form component */} {/* A pre-bound form component */}
<form {...form.getFormProps()}> <form.Provider>
<div> <form {...form.getFormProps()}>
{/* A type-safe and pre-bound field component*/} <div>
<form.Field {/* A type-safe and pre-bound field component*/}
name="firstName" <form.Field
validate={(value) => !value && "A first name is required"} name="firstName"
validateAsyncOn="change" validateOn="change"
validateAsyncDebounceMs={500} validate={(value) => !value && "A first name is required"}
validateAsync={async (value) => { validateAsyncOn="change"
await new Promise((resolve) => setTimeout(resolve, 1000)); validateAsyncDebounceMs={500}
return ( validateAsync={async (value) => {
value.includes("error") && 'No "error" allowed in first name' await new Promise((resolve) => setTimeout(resolve, 1000));
); return (
}} value.includes("error") && 'No "error" allowed in first name'
children={(field) => ( );
// Avoid hasty abstractions. Render props are great! }}
<> children={(field) => {
<input {...field.getInputProps()} /> // Avoid hasty abstractions. Render props are great!
<FieldInfo field={field} /> return (
</> <>
)} <input placeholder="uncontrolled" />
/> <input {...field.getInputProps()} />
</div> <FieldInfo field={field} />
<div> </>
<form.Field );
name="lastName" }}
children={(field) => ( />
<> </div>
<input {...field.getInputProps()} /> <div>
<FieldInfo field={field} /> <form.Field
</> name="lastName"
)} children={(field) => (
/> <>
</div> <input {...field.getInputProps()} />
<div> <FieldInfo field={field} />
<form.Field </>
name="hobbies" )}
mode="array" />
children={(hobbiesField) => ( </div>
<div> <div>
Hobbies <form.Field
<div name="hobbies"
style={{ mode="array"
paddingLeft: "1rem", children={(hobbiesField) => (
display: "flex", <div>
flexDirection: "column", Hobbies
gap: "1rem", <div
}} style={{
> paddingLeft: "1rem",
{!hobbiesField.state.value.length display: "flex",
? "No hobbies found." flexDirection: "column",
: hobbiesField.state.value.map((_, i) => ( gap: "1rem",
<div }}
key={i} >
style={{ {!hobbiesField.state.value.length
borderLeft: "2px solid gray", ? "No hobbies found."
paddingLeft: ".5rem", : hobbiesField.state.value.map((_, i) => (
}} <div
> key={i}
<hobbiesField.Field style={{
index={i} borderLeft: "2px solid gray",
name="name" paddingLeft: ".5rem",
children={(field) => {
return (
<div>
<label htmlFor={field.name}>Name:</label>
<input
name={field.name}
{...field.getInputProps()}
/>
<button
type="button"
onClick={() => hobbiesField.removeValue(i)}
>
X
</button>
<FieldInfo field={field} />
</div>
);
}} }}
/> >
<hobbiesField.Field <hobbiesField.Field
index={i} index={i}
name="description" name="name"
children={(field) => { children={(field) => {
return ( return (
<div> <div>
<label htmlFor={field.name}> <label htmlFor={field.name}>Name:</label>
Description: <input
</label> name={field.name}
<input {...field.getInputProps()}
name={field.name} />
{...field.getInputProps()} <button
/> type="button"
<FieldInfo field={field} /> onClick={() =>
</div> hobbiesField.removeValue(i)
); }
}} >
/> X
</div> </button>
))} <FieldInfo field={field} />
</div>
);
}}
/>
<hobbiesField.Field
index={i}
name="description"
children={(field) => {
return (
<div>
<label htmlFor={field.name}>
Description:
</label>
<input
name={field.name}
{...field.getInputProps()}
/>
<FieldInfo field={field} />
</div>
);
}}
/>
</div>
))}
</div>
<button
type="button"
onClick={() =>
hobbiesField.pushValue({
name: "",
description: "",
yearsOfExperience: 0,
})
}
>
Add hobby
</button>
</div> </div>
<button )}
type="button" />
onClick={() => </div>
hobbiesField.pushValue({ <form.Subscribe
name: "", selector={(state) => [state.canSubmit, state.isSubmitting]}
description: "", children={([canSubmit, isSubmitting]) => (
yearsOfExperience: 0, <button type="submit" disabled={!canSubmit}>
}) {isSubmitting ? "..." : "Submit"}
} </button>
>
Add hobby
</button>
</div>
)} )}
/> />
</div> </form>
<form.Subscribe </form.Provider>
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => (
<button type="submit" disabled={!canSubmit}>
{isSubmitting ? "..." : "Submit"}
</button>
)}
/>
</form.Form>
</div> </div>
); );
} }

View File

@@ -129,6 +129,7 @@ export function Field<TData, TFormData>({
children: (fieldApi: FieldApi<TData, TFormData>) => any children: (fieldApi: FieldApi<TData, TFormData>) => any
} & UseFieldOptions<TData, TFormData>) { } & UseFieldOptions<TData, TFormData>) {
const fieldApi = useField(fieldOptions as any) const fieldApi = useField(fieldOptions as any)
return ( return (
<formContext.Provider <formContext.Provider
value={{ formApi: fieldApi.form, parentFieldName: fieldApi.name }} value={{ formApi: fieldApi.form, parentFieldName: fieldApi.name }}

View File

@@ -15,6 +15,7 @@ declare module '@tanstack/form-core' {
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
interface FormApi<TFormData> { interface FormApi<TFormData> {
Provider: (props: { children: any }) => any
getFormProps: () => FormProps getFormProps: () => FormProps
Field: FieldComponent<TFormData, TFormData> Field: FieldComponent<TFormData, TFormData>
useField: UseField<TFormData> useField: UseField<TFormData>
@@ -40,6 +41,9 @@ export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
// @ts-ignore // @ts-ignore
const api = new FormApi<TData>(opts) const api = new FormApi<TData>(opts)
api.Provider = (props) => (
<formContext.Provider {...props} value={{ formApi: api }} />
)
api.getFormProps = () => { api.getFormProps = () => {
return { return {
onSubmit: formApi.handleSubmit, onSubmit: formApi.handleSubmit,