import type { AnyFieldLikeMeta, DeepKeys, DeepValue, FormAsyncValidateOrFn, FormValidateOrFn, UpdateMetaOptions, Updater, ValidationCause, } from '@tanstack/form-core' import type { Atom, WritableAtom } from 'jotai' import { FormApi } from '@tanstack/form-core' import { atom } from 'jotai' import { atomWithLazy } from 'jotai/vanilla/utils' type FormValidator = FormValidateOrFn | undefined type FormAsyncValidator = FormAsyncValidateOrFn | undefined export type TanStackFormApi = FormApi< TValues, FormValidator, FormValidator, FormAsyncValidator, FormValidator, FormAsyncValidator, FormValidator, FormAsyncValidator, FormValidator, FormAsyncValidator, FormAsyncValidator, TSubmitMeta > export type FormState = TanStackFormApi['state'] type FormOptionsInput = TanStackFormApi< TValues, TSubmitMeta >['options'] export type FormFieldAtomValue = { value: TValue meta: AnyFieldLikeMeta | undefined } export type FormFieldUpdate = DeepKeys> = TField extends DeepKeys ? { name: TField value: Updater> options?: UpdateMetaOptions } : never export type FormAtomInstance = { api: TanStackFormApi stateAtom: Atom> } export type FormAtom = Atom> export type FormAtoms = { formAtom: FormAtom stateAtom: Atom> valuesAtom: Atom isSubmittingAtom: Atom setFieldAtom: WritableAtom], void> fieldAtom: >( name: TField, ) => WritableAtom< FormFieldAtomValue>, [Updater>, UpdateMetaOptions?], void > validateAtom: WritableAtom> submitAtom: WritableAtom> } function createFormInstance( api: TanStackFormApi, ): FormAtomInstance { const stateAtom = atom(api.state) stateAtom.onMount = (setFormState) => { const mountCleanup = api.mount() setFormState(api.state) const subscription = api.store.subscribe(() => { setFormState(api.state) }) return () => { subscription.unsubscribe() mountCleanup() } } return { api, stateAtom, } } function setFormFieldValue>( form: FormAtomInstance, name: TField, value: Updater>, options?: UpdateMetaOptions, ) { const shouldValidate = !options?.dontValidate && !(options?.dontUpdateMeta && !form.api.getFieldMeta(name)?.isTouched) form.api.setFieldValue( name, value, shouldValidate ? options : { ...(options ?? {}), dontValidate: true }, ) if (!shouldValidate) return const fieldMeta = form.api.getFieldMeta(name) if (!fieldMeta?.errorMap.onSubmit) return if (fieldMeta.errorMap.onChange || fieldMeta.errorMap.onDynamic) return form.api.setFieldMeta(name, (prev) => ({ ...prev, errorMap: { ...prev.errorMap, onSubmit: undefined, }, errorSourceMap: { ...prev.errorSourceMap, onSubmit: undefined, }, })) } export function atomWithForm( options: FormOptionsInput, ): FormAtom { return atomWithLazy(() => createFormInstance(new FormApi(options))) } export function createFormAtoms( formAtom: FormAtom, ): FormAtoms { const stateAtom = atom((get) => { const form = get(formAtom) return get(form.stateAtom) }) const valuesAtom = atom((get) => { return get(stateAtom).values }) const isSubmittingAtom = atom((get) => { return get(stateAtom).isSubmitting }) const setFieldAtom = atom], void>(null, (get, _set, update) => { setFormFieldValue(get(formAtom), update.name, update.value, update.options) }) function fieldAtom>( name: TField, ): WritableAtom< FormFieldAtomValue>, [Updater>, UpdateMetaOptions?], void > { return atom( (get) => { const form = get(formAtom) const state = get(form.stateAtom) const api = form.api return { value: api.getFieldValue(name), meta: state.fieldMeta[name], } }, (get, _set, value, options) => { setFormFieldValue(get(formAtom), name, value, options) }, ) } const validateAtom = atom>( null, (get, _set, cause) => { return get(formAtom).api.validate(cause) }, ) const submitAtom = atom>(null, (get) => { return get(formAtom).api.handleSubmit() }) return { formAtom, stateAtom, valuesAtom, isSubmittingAtom, setFieldAtom, fieldAtom, validateAtom, submitAtom, } }