mirror of
https://github.com/LukeHagar/form.git
synced 2025-12-06 04:19:43 +00:00
feat: remove getFormProps to support React Native
This commit is contained in:
committed by
Corbin Crutchley
parent
0d7e545c1d
commit
2a31bd8219
@@ -7,10 +7,6 @@ title: Form API
|
|||||||
|
|
||||||
When using `@tanstack/react-form`, the [core form API](../../reference/formApi) is extended with additional methods for React-specific functionality:
|
When using `@tanstack/react-form`, the [core form API](../../reference/formApi) is extended with additional methods for React-specific functionality:
|
||||||
|
|
||||||
- ```tsx
|
|
||||||
getFormProps: () => FormProps
|
|
||||||
```
|
|
||||||
- A function that returns props for the form element.
|
|
||||||
- ```tsx
|
- ```tsx
|
||||||
Field: FieldComponent<TFormData>
|
Field: FieldComponent<TFormData>
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -26,20 +26,20 @@ In the example below, you can see TanStack Form in action with the React framewo
|
|||||||
[Open in CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-form/tree/main/examples/react/simple)
|
[Open in CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-form/tree/main/examples/react/simple)
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from 'react-dom/client'
|
||||||
import { useForm } from "@tanstack/react-form";
|
import { useForm } from '@tanstack/react-form'
|
||||||
import type { FieldApi } from "@tanstack/react-form";
|
import type { FieldApi } from '@tanstack/react-form'
|
||||||
|
|
||||||
function FieldInfo({ field }: { field: FieldApi<any, any> }) {
|
function FieldInfo({ field }: { field: FieldApi<any, any> }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{field.state.meta.touchedError ? (
|
{field.state.meta.touchedError ? (
|
||||||
<em>{field.state.meta.touchedError}</em>
|
<em>{field.state.meta.touchedError}</em>
|
||||||
) : null}{" "}
|
) : null}{' '}
|
||||||
{field.state.meta.isValidating ? "Validating..." : null}
|
{field.state.meta.isValidating ? 'Validating...' : null}
|
||||||
</>
|
</>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
@@ -47,40 +47,46 @@ export default function App() {
|
|||||||
// Memoize your default values to prevent re-renders
|
// Memoize your default values to prevent re-renders
|
||||||
defaultValues: React.useMemo(
|
defaultValues: React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
firstName: "",
|
firstName: '',
|
||||||
lastName: "",
|
lastName: '',
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
),
|
),
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
// Do something with form data
|
// Do something with form data
|
||||||
console.log(values);
|
console.log(values)
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Simple Form Example</h1>
|
<h1>Simple Form Example</h1>
|
||||||
{/* A pre-bound form component */}
|
{/* A pre-bound form component */}
|
||||||
<form.Provider>
|
<form.Provider>
|
||||||
<form {...form.getFormProps()}>
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
void form.handleSubmit()
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
{/* A type-safe and pre-bound field component*/}
|
{/* A type-safe and pre-bound field component*/}
|
||||||
<form.Field
|
<form.Field
|
||||||
name="firstName"
|
name="firstName"
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
!value
|
!value
|
||||||
? "A first name is required"
|
? 'A first name is required'
|
||||||
: value.length < 3
|
: value.length < 3
|
||||||
? "First name must be at least 3 characters"
|
? 'First name must be at least 3 characters'
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
onChangeAsyncDebounceMs={500}
|
onChangeAsyncDebounceMs={500}
|
||||||
onChangeAsync={async (value) => {
|
onChangeAsync={async (value) => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||||
return (
|
return (
|
||||||
value.includes("error") && 'No "error" allowed in first name'
|
value.includes('error') && 'No "error" allowed in first name'
|
||||||
);
|
)
|
||||||
}}
|
}}
|
||||||
children={(field) => {
|
children={(field) => {
|
||||||
// Avoid hasty abstractions. Render props are great!
|
// Avoid hasty abstractions. Render props are great!
|
||||||
@@ -90,7 +96,7 @@ export default function App() {
|
|||||||
<input name={field.name} {...field.getInputProps()} />
|
<input name={field.name} {...field.getInputProps()} />
|
||||||
<FieldInfo field={field} />
|
<FieldInfo field={field} />
|
||||||
</>
|
</>
|
||||||
);
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,19 +116,19 @@ export default function App() {
|
|||||||
selector={(state) => [state.canSubmit, state.isSubmitting]}
|
selector={(state) => [state.canSubmit, state.isSubmitting]}
|
||||||
children={([canSubmit, isSubmitting]) => (
|
children={([canSubmit, isSubmitting]) => (
|
||||||
<button type="submit" disabled={!canSubmit}>
|
<button type="submit" disabled={!canSubmit}>
|
||||||
{isSubmitting ? "..." : "Submit"}
|
{isSubmitting ? '...' : 'Submit'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</form.Provider>
|
</form.Provider>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootElement = document.getElementById("root")!;
|
const rootElement = document.getElementById('root')!
|
||||||
|
|
||||||
ReactDOM.createRoot(rootElement).render(<App />);
|
ReactDOM.createRoot(rootElement).render(<App />)
|
||||||
```
|
```
|
||||||
|
|
||||||
## You talked me into it, so what now?
|
## You talked me into it, so what now?
|
||||||
|
|||||||
@@ -35,7 +35,13 @@ export default function App() {
|
|||||||
<h1>Simple Form Example</h1>
|
<h1>Simple Form Example</h1>
|
||||||
{/* A pre-bound form component */}
|
{/* A pre-bound form component */}
|
||||||
<form.Provider>
|
<form.Provider>
|
||||||
<form {...form.getFormProps()}>
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
void form.handleSubmit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
{/* A type-safe and pre-bound field component*/}
|
{/* A type-safe and pre-bound field component*/}
|
||||||
<form.Field
|
<form.Field
|
||||||
|
|||||||
@@ -4,16 +4,6 @@ import type { DeepKeys, DeepValue, Updater } from './utils'
|
|||||||
import { functionalUpdate, getBy, setBy } from './utils'
|
import { functionalUpdate, getBy, setBy } from './utils'
|
||||||
import type { FieldApi, FieldMeta, ValidationCause } from './FieldApi'
|
import type { FieldApi, FieldMeta, ValidationCause } from './FieldApi'
|
||||||
|
|
||||||
export interface Register {
|
|
||||||
// FormSubmitEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FormSubmitEvent = Register extends {
|
|
||||||
FormSubmitEvent: infer E
|
|
||||||
}
|
|
||||||
? E
|
|
||||||
: Event
|
|
||||||
|
|
||||||
export type FormOptions<TData> = {
|
export type FormOptions<TData> = {
|
||||||
defaultValues?: TData
|
defaultValues?: TData
|
||||||
defaultState?: Partial<FormState<TData>>
|
defaultState?: Partial<FormState<TData>>
|
||||||
@@ -223,12 +213,7 @@ export class FormApi<TFormData> {
|
|||||||
return Promise.all(fieldValidationPromises)
|
return Promise.all(fieldValidationPromises)
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateForm = async () => {}
|
handleSubmit = async () => {
|
||||||
|
|
||||||
handleSubmit = async (e: FormSubmitEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
// Check to see that the form and all fields have been touched
|
// Check to see that the form and all fields have been touched
|
||||||
// If they have not, touch them all and run validation
|
// If they have not, touch them all and run validation
|
||||||
// Run form validation
|
// Run form validation
|
||||||
@@ -236,7 +221,7 @@ export class FormApi<TFormData> {
|
|||||||
|
|
||||||
this.store.setState((old) => ({
|
this.store.setState((old) => ({
|
||||||
...old,
|
...old,
|
||||||
// Submittion attempts mark the form as not submitted
|
// Submission attempts mark the form as not submitted
|
||||||
isSubmitted: false,
|
isSubmitted: false,
|
||||||
// Count submission attempts
|
// Count submission attempts
|
||||||
submissionAttempts: old.submissionAttempts + 1,
|
submissionAttempts: old.submissionAttempts + 1,
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ export type {
|
|||||||
|
|
||||||
export { FormApi, FieldApi, functionalUpdate } from '@tanstack/form-core'
|
export { FormApi, FieldApi, functionalUpdate } from '@tanstack/form-core'
|
||||||
|
|
||||||
export type { FormProps } from './useForm'
|
|
||||||
export { useForm } from './useForm'
|
export { useForm } from './useForm'
|
||||||
|
|
||||||
export type { UseField, FieldComponent } from './useField'
|
export type { UseField, FieldComponent } from './useField'
|
||||||
|
|||||||
@@ -6,17 +6,10 @@ import React from 'react'
|
|||||||
import { type UseField, type FieldComponent, Field, useField } from './useField'
|
import { type UseField, type FieldComponent, Field, useField } from './useField'
|
||||||
import { formContext } from './formContext'
|
import { formContext } from './formContext'
|
||||||
|
|
||||||
export type FormSubmitEvent = React.FormEvent<HTMLFormElement>
|
|
||||||
|
|
||||||
declare module '@tanstack/form-core' {
|
declare module '@tanstack/form-core' {
|
||||||
interface Register {
|
|
||||||
FormSubmitEvent: FormSubmitEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-shadow
|
// eslint-disable-next-line no-shadow
|
||||||
interface FormApi<TFormData> {
|
interface FormApi<TFormData> {
|
||||||
Provider: (props: { children: any }) => any
|
Provider: (props: { children: any }) => any
|
||||||
getFormProps: () => FormProps
|
|
||||||
Field: FieldComponent<TFormData, TFormData>
|
Field: FieldComponent<TFormData, TFormData>
|
||||||
useField: UseField<TFormData>
|
useField: UseField<TFormData>
|
||||||
useStore: <TSelected = NoInfer<FormState<TFormData>>>(
|
useStore: <TSelected = NoInfer<FormState<TFormData>>>(
|
||||||
@@ -31,11 +24,6 @@ declare module '@tanstack/form-core' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FormProps = {
|
|
||||||
onSubmit: (e: FormSubmitEvent) => void
|
|
||||||
disabled: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
|
export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
|
||||||
const [formApi] = React.useState(() => {
|
const [formApi] = React.useState(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -45,12 +33,6 @@ export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
|
|||||||
api.Provider = (props) => (
|
api.Provider = (props) => (
|
||||||
<formContext.Provider {...props} value={{ formApi: api }} />
|
<formContext.Provider {...props} value={{ formApi: api }} />
|
||||||
)
|
)
|
||||||
api.getFormProps = () => {
|
|
||||||
return {
|
|
||||||
onSubmit: formApi.handleSubmit,
|
|
||||||
disabled: api.state.isSubmitting,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
api.Field = Field as any
|
api.Field = Field as any
|
||||||
api.useField = useField as any
|
api.useField = useField as any
|
||||||
api.useStore = (
|
api.useStore = (
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"lib": ["DOM", "DOM.Iterable", "ES2020"],
|
"lib": ["ES2020"],
|
||||||
"module": "ES2020",
|
"module": "ES2020",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user