fix: complete form factory functionality, docs

This commit is contained in:
Tanner Linsley
2023-05-01 10:59:37 -06:00
parent b274bccf3d
commit c444704ae3
7 changed files with 103 additions and 51 deletions

View File

@@ -0,0 +1,36 @@
---
id: createFormFactory
title: createFormFactory
---
### `createFormFactory`
```tsx
export function createFormFactory<TFormData>(
opts?: FormOptions<TFormData>,
): FormFactory<TFormData>
```
A function that creates a new `FormFactory<TFormData>` instance.
- `opts`
- Optional form options and a `listen` function to be called with the form state.
### `FormFactory<TFormData>`
A type representing a form factory. Form factories provide a type-safe way to interact with the form API as opposed to using the globally exported form utilities.
```tsx
export type FormFactory<TFormData> = {
useForm: (opts?: FormOptions<TFormData>) => FormApi<TFormData>
useField: UseField<TFormData>
Field: FieldComponent<TFormData>
}
```
- `useForm`
- A custom hook that creates and returns a new instance of the `FormApi<TFormData>` class.
- `useField`
- A custom hook that returns an instance of the `FieldApi<TFormData>` class.
- `Field`
- A form field component.

View File

@@ -35,10 +35,6 @@ An object representing the options for a form.
validate?: (values: TData, formApi: FormApi<TData>) => Promise<any>
```
- A function for custom validation logic for the form.
- ```tsx
debugForm?: boolean
```
- A boolean flag to enable or disable form debugging.
- ```tsx
defaultValidatePristine?: boolean
```

View File

@@ -2,10 +2,22 @@ import React from "react";
import ReactDOM from "react-dom/client";
import { FieldApi, createFormFactory } from "@tanstack/react-form";
const formFactory = createFormFactory({
type Person = {
firstName: string;
lastName: string;
hobbies: Hobby[];
};
type Hobby = {
name: string;
description: string;
};
const formFactory = createFormFactory<Person>({
defaultValues: {
firstName: "",
lastName: "",
hobbies: [],
},
});
@@ -68,6 +80,18 @@ export default function App() {
)}
/>
</div>
<div>
<form.Field
name="hobbies"
children={(field) => (
<>
<label htmlFor={field.name}>Last Name:</label>
<input name={field.name} {...field.getInputProps()} />
<FieldInfo field={field} />
</>
)}
/>
</div>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => (

View File

@@ -1,4 +1,3 @@
import type { FormEvent } from 'react'
import { Store } from '@tanstack/store'
//
import type { DeepKeys, DeepValue, Updater } from './utils'
@@ -11,7 +10,6 @@ export type FormOptions<TData> = {
onSubmit?: (values: TData, formApi: FormApi<TData>) => void
onInvalidSubmit?: (values: TData, formApi: FormApi<TData>) => void
validate?: (values: TData, formApi: FormApi<TData>) => Promise<any>
debugForm?: boolean
defaultValidatePristine?: boolean
defaultValidateOn?: ValidationCause
defaultValidateAsyncOn?: ValidationCause
@@ -53,7 +51,7 @@ export type FormState<TData> = {
submissionAttempts: number
}
export function getDefaultFormState<TData>(
function getDefaultFormState<TData>(
defaultState: Partial<FormState<TData>>,
): FormState<TData> {
return {
@@ -247,7 +245,7 @@ export class FormApi<TFormData> {
return this.validationMeta.validationPromise
}
handleSubmit = async (e: FormEvent & { __handled?: boolean }) => {
handleSubmit = async (e: Event) => {
e.preventDefault()
e.stopPropagation()

View File

@@ -1,10 +1,12 @@
import type { FormApi, FormOptions } from '@tanstack/form-core'
import { createUseField, type UseField } from './useField'
import { useForm } from './useForm'
import { createFieldComponent, type FieldComponent } from './Field'
export type FormFactory<TFormData> = {
useForm: (opts?: FormOptions<TFormData>) => FormApi<TFormData>
useField: UseField<TFormData>
Field: FieldComponent<TFormData>
}
export function createFormFactory<TFormData>(
@@ -14,6 +16,7 @@ export function createFormFactory<TFormData>(
useForm: (opts) => {
return useForm<TFormData>({ ...defaultOpts, ...opts } as any) as any
},
useField: createUseField(),
useField: createUseField<TFormData>(),
Field: createFieldComponent<TFormData>(),
}
}

View File

@@ -1,5 +1,35 @@
export * from '@tanstack/form-core'
export * from './useForm'
export * from './Field'
export * from './useField'
export * from './createFormFactory'
export type {
ChangeProps,
DeepKeys,
DeepValue,
FieldApiOptions,
FieldInfo,
FieldMeta,
FieldOptions,
FieldState,
FormOptions,
FormState,
InputProps,
RequiredByKey,
Updater,
UpdaterFn,
UserChangeProps,
UserInputProps,
ValidationCause,
ValidationError,
ValidationMeta,
} from '@tanstack/form-core'
export { FormApi, FieldApi, functionalUpdate } from '@tanstack/form-core'
export type { FormComponent, FormProps } from './useForm'
export { useForm } from './useForm'
export type { FieldComponent } from './Field'
export { Field } from './Field'
export type { UseField } from './useField'
export { useField } from './useField'
export type { FormFactory } from './createFormFactory'
export { createFormFactory } from './createFormFactory'

View File

@@ -54,8 +54,6 @@ export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
return api
})
// React.useEffect(() => formApi.mount(), [])
return formApi as any
}
@@ -66,7 +64,7 @@ export type FormProps = React.HTMLProps<HTMLFormElement> & {
export type FormComponent = (props: FormProps) => any
export function createFormComponent(formApi: FormApi<any>) {
function createFormComponent(formApi: FormApi<any>) {
const Form: FormComponent = ({ children, noFormElement, ...rest }) => {
const isSubmitting = formApi.useStore((state) => state.isSubmitting)
@@ -80,26 +78,6 @@ export function createFormComponent(formApi: FormApi<any>) {
disabled={isSubmitting}
{...rest}
>
{formApi.options.debugForm ? (
<div
style={{
margin: '2rem 0',
}}
>
<div
style={{
fontWeight: 'bolder',
}}
>
Form State
</div>
<pre>
<code>
{JSON.stringify(formApi, safeStringifyReplace(), 2)}
</code>
</pre>
</div>
) : null}
{children}
</form>
)}
@@ -109,16 +87,3 @@ export function createFormComponent(formApi: FormApi<any>) {
return Form
}
function safeStringifyReplace() {
const set = new Set()
return (_key: string, value: any) => {
if (typeof value === 'object' || Array.isArray(value)) {
if (set.has(value)) {
return '(circular value)'
}
set.add(value)
}
return typeof value === 'function' ? undefined : value
}
}