fix: createFormFactory

This commit is contained in:
Tanner Linsley
2023-05-01 10:27:51 -06:00
parent 09475161af
commit b274bccf3d
10 changed files with 3650 additions and 320 deletions

View File

@@ -1,6 +1,13 @@
import React from "react"; import React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import { useForm, FieldApi } from "@tanstack/react-form"; import { FieldApi, createFormFactory } from "@tanstack/react-form";
const formFactory = createFormFactory({
defaultValues: {
firstName: "",
lastName: "",
},
});
function FieldInfo({ field }: { field: FieldApi<any, any> }) { function FieldInfo({ field }: { field: FieldApi<any, any> }) {
return ( return (
@@ -14,15 +21,7 @@ function FieldInfo({ field }: { field: FieldApi<any, any> }) {
} }
export default function App() { export default function App() {
const form = useForm({ const form = formFactory.useForm({
// Memoize your default values to prevent re-renders
defaultValues: React.useMemo(
() => ({
firstName: "",
lastName: "",
}),
[]
),
onSubmit: async (values) => { onSubmit: async (values) => {
// Do something with form data // Do something with form data
console.log(values); console.log(values);

View File

@@ -105,7 +105,7 @@
"stream-to-array": "^2.3.0", "stream-to-array": "^2.3.0",
"ts-jest": "^27.1.1", "ts-jest": "^27.1.1",
"ts-node": "^10.7.0", "ts-node": "^10.7.0",
"typescript": "^4.7.4", "typescript": "^5.0.4",
"vue": "^3.2.33" "vue": "^3.2.33"
}, },
"bundlewatch": { "bundlewatch": {

View File

@@ -5,10 +5,9 @@ import { Store } from '@tanstack/store'
export type ValidationCause = 'change' | 'blur' | 'submit' export type ValidationCause = 'change' | 'blur' | 'submit'
export type FieldOptions<TData, TFormData> = { export interface FieldOptions<TData, TFormData> {
name: unknown extends TFormData ? string : DeepKeys<TFormData> name: unknown extends TFormData ? string : DeepKeys<TFormData>
defaultValue?: TData defaultValue?: TData
form?: FormApi<TFormData>
validate?: ( validate?: (
value: TData, value: TData,
fieldApi: FieldApi<TData, TFormData>, fieldApi: FieldApi<TData, TFormData>,
@@ -24,6 +23,13 @@ export type FieldOptions<TData, TFormData> = {
defaultMeta?: Partial<FieldMeta> defaultMeta?: Partial<FieldMeta>
} }
export type FieldApiOptions<TData, TFormData> = FieldOptions<
TData,
TFormData
> & {
form: FormApi<TFormData>
}
export type FieldMeta = { export type FieldMeta = {
isTouched: boolean isTouched: boolean
touchedError?: ValidationError touchedError?: ValidationError
@@ -53,11 +59,6 @@ export type InputProps = {
onBlur: (event: any) => void onBlur: (event: any) => void
} }
export type FieldApiOptions<TData, TFormData> = RequiredByKey<
FieldOptions<TData, TFormData>,
'form'
>
let uid = 0 let uid = 0
export type FieldState<TData> = { export type FieldState<TData> = {

View File

@@ -5,7 +5,6 @@ import {
type DeepValue, type DeepValue,
type FieldApi, type FieldApi,
type FieldOptions, type FieldOptions,
type FormApi,
} from '@tanstack/form-core' } from '@tanstack/form-core'
import { useField } from './useField' import { useField } from './useField'
@@ -19,9 +18,9 @@ export type FieldComponent<TFormData> = <TField extends DeepKeys<TFormData>>({
name: TField name: TField
} & Omit<FieldOptions<DeepValue<TFormData, TField>, TFormData>, 'name'>) => any } & Omit<FieldOptions<DeepValue<TFormData, TField>, TFormData>, 'name'>) => any
export function createFieldComponent<TFormData>(formApi: FormApi<TFormData>) { export function createFieldComponent<TFormData>() {
const ConnectedField: FieldComponent<TFormData> = (props) => ( const ConnectedField: FieldComponent<TFormData> = (props) => (
<Field {...(props as any)} form={formApi} /> <Field {...(props as any)} />
) )
return ConnectedField return ConnectedField
} }

View File

@@ -0,0 +1,19 @@
import type { FormApi, FormOptions } from '@tanstack/form-core'
import { createUseField, type UseField } from './useField'
import { useForm } from './useForm'
export type FormFactory<TFormData> = {
useForm: (opts?: FormOptions<TFormData>) => FormApi<TFormData>
useField: UseField<TFormData>
}
export function createFormFactory<TFormData>(
defaultOpts?: FormOptions<TFormData>,
): FormFactory<TFormData> {
return {
useForm: (opts) => {
return useForm<TFormData>({ ...defaultOpts, ...opts } as any) as any
},
useField: createUseField(),
}
}

View File

@@ -3,13 +3,9 @@ import * as React from 'react'
export const formContext = React.createContext<FormApi<any> | null>(null) export const formContext = React.createContext<FormApi<any> | null>(null)
export function useFormContext(customFormApi?: FormApi<any>) { export function useFormContext() {
const formApi = React.useContext(formContext) const formApi = React.useContext(formContext)
if (customFormApi) {
return customFormApi
}
if (!formApi) { if (!formApi) {
throw new Error(`You are trying to use the form API outside of a form!`) throw new Error(`You are trying to use the form API outside of a form!`)
} }

View File

@@ -2,3 +2,4 @@ export * from '@tanstack/form-core'
export * from './useForm' export * from './useForm'
export * from './Field' export * from './Field'
export * from './useField' export * from './useField'
export * from './createFormFactory'

View File

@@ -1,14 +1,17 @@
import * as React from 'react' import * as React from 'react'
// //
import { useStore } from '@tanstack/react-store' import { useStore } from '@tanstack/react-store'
import type { import type { DeepKeys, DeepValue, FieldOptions } from '@tanstack/form-core'
DeepKeys,
DeepValue,
FieldOptions,
FormApi,
} from '@tanstack/form-core'
import { FieldApi } from '@tanstack/form-core' import { FieldApi } from '@tanstack/form-core'
import { useFormContext } from './formContext' import { useFormContext } from './formContext'
import type { FormFactory } from './createFormFactory'
declare module '@tanstack/form-core' {
// eslint-disable-next-line no-shadow
interface FieldOptions<TData, TFormData> {
formFactory?: FormFactory<TFormData>
}
}
export type UseField<TFormData> = <TField extends DeepKeys<TFormData>>( export type UseField<TFormData> = <TField extends DeepKeys<TFormData>>(
opts?: { name: TField } & FieldOptions< opts?: { name: TField } & FieldOptions<
@@ -17,12 +20,10 @@ export type UseField<TFormData> = <TField extends DeepKeys<TFormData>>(
>, >,
) => FieldApi<DeepValue<TFormData, TField>, TFormData> ) => FieldApi<DeepValue<TFormData, TField>, TFormData>
export function createUseField<TFormData>(formApi: FormApi<TFormData>) { export function createUseField<TFormData>(): UseField<TFormData> {
const useFormField: UseField<TFormData> = (opts) => { return (opts) => {
return useField({ ...opts, form: formApi } as any) return useField(opts as any)
} }
return useFormField
} }
export function useField<TData, TFormData>( export function useField<TData, TFormData>(
@@ -30,13 +31,8 @@ export function useField<TData, TFormData>(
// selector: (state: FieldApi<TData, TFormData>) => TSelected // selector: (state: FieldApi<TData, TFormData>) => TSelected
}, },
): FieldApi<TData, TFormData> { ): FieldApi<TData, TFormData> {
// invariant( // TODO:
// opts.name,
// `useField: A field is required to use this hook. eg, useField('myField', options)`
// )
// Get the form API either manually or from context // Get the form API either manually or from context
const formApi = useFormContext(opts.form) const formApi = useFormContext()
const [fieldApi] = React.useState<FieldApi<TData, TFormData>>( const [fieldApi] = React.useState<FieldApi<TData, TFormData>>(
() => new FieldApi({ ...opts, form: formApi }), () => new FieldApi({ ...opts, form: formApi }),

View File

@@ -24,22 +24,21 @@ declare module '@tanstack/form-core' {
}) => any }) => any
} }
} }
//
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
const api = new FormApi<TData>(opts || {}) const api = new FormApi<TData>(opts)
api.Form = createFormComponent(api) api.Form = createFormComponent(api)
api.Field = createFieldComponent(api) api.Field = createFieldComponent<TData>()
api.useField = createUseField(api) api.useField = createUseField<TData>()
api.useStore = ( api.useStore = (
// @ts-ignore // @ts-ignore
selector, selector,
) => { ) => {
// eslint-disable-next-line react-hooks/rules-of-hooks // eslint-disable-next-line react-hooks/rules-of-hooks
return useStore(api.store, selector) as any return useStore(api.store, selector as any) as any
} }
api.Subscribe = ( api.Subscribe = (
// @ts-ignore // @ts-ignore
@@ -48,7 +47,7 @@ export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
return functionalUpdate( return functionalUpdate(
props.children, props.children,
// eslint-disable-next-line react-hooks/rules-of-hooks // eslint-disable-next-line react-hooks/rules-of-hooks
useStore(api.store, props.selector), useStore(api.store, props.selector as any),
) as any ) as any
} }
@@ -57,7 +56,7 @@ export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
// React.useEffect(() => formApi.mount(), []) // React.useEffect(() => formApi.mount(), [])
return formApi return formApi as any
} }
export type FormProps = React.HTMLProps<HTMLFormElement> & { export type FormProps = React.HTMLProps<HTMLFormElement> & {

3860
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff