feat: remove getFormProps to support React Native

This commit is contained in:
Corbin Crutchley
2023-09-03 01:00:45 -07:00
committed by Corbin Crutchley
parent 0d7e545c1d
commit 2a31bd8219
7 changed files with 38 additions and 64 deletions

View File

@@ -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>
``` ```

View File

@@ -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?

View File

@@ -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

View File

@@ -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,

View File

@@ -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'

View File

@@ -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 = (

View File

@@ -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,