mirror of
https://github.com/LukeHagar/form.git
synced 2025-12-07 20:37:48 +00:00
122 lines
3.4 KiB
TypeScript
122 lines
3.4 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 from 'react'
|
|
import { createFieldComponent, type FieldComponent } from './Field'
|
|
import { createUseField, type UseField } from './useField'
|
|
import { formContext } from './formContext'
|
|
//
|
|
|
|
declare module '@tanstack/form-core' {
|
|
// eslint-disable-next-line no-shadow
|
|
interface FormApi<TFormData> {
|
|
Form: FormComponent
|
|
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>) => React.ReactNode)
|
|
| React.ReactNode
|
|
}) => any
|
|
}
|
|
}
|
|
|
|
export function useForm<TData>(
|
|
opts?: FormOptions<TData> & { listen?: (state: FormState<TData>) => any },
|
|
): FormApi<TData> {
|
|
// & { listened: TListen }
|
|
const [formApi] = React.useState(() => {
|
|
const api = new FormApi<TData>(opts || {})
|
|
|
|
api.Form = createFormComponent(api)
|
|
api.Field = createFieldComponent(api)
|
|
api.useField = createUseField(api)
|
|
api.useStore = (selector) => {
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
return useStore(api.store, selector) as any
|
|
}
|
|
api.Subscribe = (props) => {
|
|
return functionalUpdate(
|
|
props.children,
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
useStore(api.store, props.selector),
|
|
) as any
|
|
}
|
|
|
|
return api
|
|
})
|
|
|
|
// React.useEffect(() => formApi.mount(), [])
|
|
|
|
return formApi
|
|
}
|
|
|
|
export type FormProps = React.HTMLProps<HTMLFormElement> & {
|
|
children: React.ReactNode
|
|
noFormElement?: boolean
|
|
}
|
|
|
|
export type FormComponent = (props: FormProps) => any
|
|
|
|
export function createFormComponent(formApi: FormApi<any>) {
|
|
const Form: FormComponent = ({ children, noFormElement, ...rest }) => {
|
|
const isSubmitting = formApi.useStore((state) => state.isSubmitting)
|
|
|
|
return (
|
|
<formContext.Provider value={formApi}>
|
|
{noFormElement ? (
|
|
children
|
|
) : (
|
|
<form
|
|
onSubmit={formApi.handleSubmit}
|
|
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>
|
|
)}
|
|
</formContext.Provider>
|
|
)
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|