Files
form/packages/react-form/src/useForm.tsx

69 lines
2.2 KiB
TypeScript

import type { FormState, FormOptions } from '@tanstack/form-core'
import { FormApi, functionalUpdate } from '@tanstack/form-core'
import type { NoInfer } from '@tanstack/react-store'
import { useStore } from '@tanstack/react-store'
import React, { type ReactNode, useState } from 'react'
import { type UseField, type FieldComponent, Field, useField } from './useField'
import { formContext } from './formContext'
import useIsomorphicLayoutEffect from 'use-isomorphic-layout-effect'
declare module '@tanstack/form-core' {
// eslint-disable-next-line no-shadow
interface FormApi<TFormData> {
Provider: (props: { children: any }) => any
Field: FieldComponent<TFormData>
useField: UseField<TFormData>
useStore: <TSelected = NoInfer<FormState<TFormData>>>(
selector?: (state: NoInfer<FormState<TFormData>>) => TSelected,
) => TSelected
Subscribe: <TSelected = NoInfer<FormState<TFormData>>>(props: {
selector?: (state: NoInfer<FormState<TFormData>>) => TSelected
children: ((state: NoInfer<TSelected>) => ReactNode) | ReactNode
}) => any
}
}
export function useForm<TData>(opts?: FormOptions<TData>): FormApi<TData> {
const [formApi] = useState(() => {
// @ts-ignore
const api = new FormApi<TData>(opts)
api.Provider = function Provider(props) {
return <formContext.Provider {...props} value={{ formApi: api }} />
}
api.Field = Field as any
api.useField = useField as any
api.useStore = (
// @ts-ignore
selector,
) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
return useStore(api.store as any, selector as any) as any
}
api.Subscribe = (
// @ts-ignore
props,
) => {
return functionalUpdate(
props.children,
// eslint-disable-next-line react-hooks/rules-of-hooks
useStore(api.store as any, props.selector as any),
) as any
}
return api
})
formApi.useStore((state) => state.isSubmitting)
/**
* formApi.update should not have any side effects. Think of it like a `useRef`
* that we need to keep updated every render with the most up-to-date information.
*/
useIsomorphicLayoutEffect(() => {
formApi.update(opts)
})
return formApi as any
}