diff --git a/examples/react/next-server-actions-zod/package.json b/examples/react/next-server-actions-zod/package.json index 4db1c5f1a..20297d27a 100644 --- a/examples/react/next-server-actions-zod/package.json +++ b/examples/react/next-server-actions-zod/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "@tanstack/react-form-nextjs": "^1.28.3", - "@tanstack/react-store": "^0.8.1", + "@tanstack/react-store": "^0.9.1", "next": "16.0.5", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/examples/react/next-server-actions/package.json b/examples/react/next-server-actions/package.json index 6ab2dc9de..31549bde7 100644 --- a/examples/react/next-server-actions/package.json +++ b/examples/react/next-server-actions/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "@tanstack/react-form-nextjs": "^1.28.3", - "@tanstack/react-store": "^0.8.1", + "@tanstack/react-store": "^0.9.1", "next": "16.0.5", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/examples/react/remix/package.json b/examples/react/remix/package.json index 524d3115b..b7c4b56d2 100644 --- a/examples/react/remix/package.json +++ b/examples/react/remix/package.json @@ -12,7 +12,7 @@ "@remix-run/react": "^2.17.1", "@remix-run/serve": "^2.17.1", "@tanstack/react-form-remix": "^1.28.3", - "@tanstack/react-store": "^0.8.1", + "@tanstack/react-store": "^0.9.1", "isbot": "^5.1.30", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/examples/react/tanstack-start/package.json b/examples/react/tanstack-start/package.json index 473bc9a16..bd1757cd3 100644 --- a/examples/react/tanstack-start/package.json +++ b/examples/react/tanstack-start/package.json @@ -14,7 +14,7 @@ "@tanstack/react-form-start": "^1.28.3", "@tanstack/react-router": "^1.134.9", "@tanstack/react-start": "^1.134.9", - "@tanstack/react-store": "^0.8.1", + "@tanstack/react-store": "^0.9.1", "react": "^19.0.0", "react-dom": "^19.0.0" }, diff --git a/packages/angular-form/package.json b/packages/angular-form/package.json index df2180d16..f75ed4abd 100644 --- a/packages/angular-form/package.json +++ b/packages/angular-form/package.json @@ -42,7 +42,7 @@ "src" ], "dependencies": { - "@tanstack/angular-store": "^0.8.1", + "@tanstack/angular-store": "^0.9.1", "@tanstack/form-core": "workspace:*", "tslib": "^2.8.1" }, diff --git a/packages/form-core/package.json b/packages/form-core/package.json index 6424cb144..6e3338de9 100644 --- a/packages/form-core/package.json +++ b/packages/form-core/package.json @@ -53,7 +53,7 @@ "dependencies": { "@tanstack/devtools-event-client": "^0.4.0", "@tanstack/pacer-lite": "^0.1.1", - "@tanstack/store": "^0.8.1" + "@tanstack/store": "^0.9.1" }, "devDependencies": { "arktype": "^2.1.22", diff --git a/packages/form-core/src/FieldApi.ts b/packages/form-core/src/FieldApi.ts index 1d5ba3096..f8b1b009e 100644 --- a/packages/form-core/src/FieldApi.ts +++ b/packages/form-core/src/FieldApi.ts @@ -1,4 +1,4 @@ -import { Derived, batch } from '@tanstack/store' +import { batch, createStore } from '@tanstack/store' import { isStandardSchemaValidator, standardSchemaValidators, @@ -8,11 +8,11 @@ import { determineFieldLevelErrorSourceAndValue, evaluate, getAsyncValidatorArray, - getBy, getSyncValidatorArray, mergeOpts, } from './utils' import { defaultValidationLogic } from './ValidationLogic' +import type { ReadonlyStore } from '@tanstack/store' import type { DeepKeys, DeepValue, UnwrapOneLevelOfArray } from './util-types' import type { StandardSchemaV1, @@ -1090,7 +1090,7 @@ export class FieldApi< /** * The field state store. */ - store!: Derived< + store!: ReadonlyStore< FieldState< TParentData, TName, @@ -1167,10 +1167,9 @@ export class FieldApi< formListeners: {} as Record, } - this.store = new Derived({ - deps: [this.form.store], - fn: ({ prevVal: _prevVal }) => { - const prevVal = _prevVal as + this.store = createStore( + ( + prevVal: | FieldState< TParentData, TName, @@ -1194,7 +1193,10 @@ export class FieldApi< TFormOnDynamic, TFormOnDynamicAsync > - | undefined + | undefined, + ) => { + // Temp hack to subscribe to form.store + this.form.store.get() const meta = this.form.getFieldMeta(this.name) ?? { ...defaultFieldMeta, @@ -1242,7 +1244,7 @@ export class FieldApi< TFormOnDynamicAsync > }, - }) + ) } /** @@ -1275,8 +1277,6 @@ export class FieldApi< * Mounts the field instance to the form. */ mount = () => { - const cleanup = this.store.mount() - if (this.options.defaultValue !== undefined && !this.getMeta().isTouched) { this.form.setFieldValue(this.name, this.options.defaultValue, { dontUpdateMeta: true, @@ -1322,7 +1322,8 @@ export class FieldApi< fieldApi: this, }) - return cleanup + // TODO: Remove + return () => {} } /** diff --git a/packages/form-core/src/FieldGroupApi.ts b/packages/form-core/src/FieldGroupApi.ts index e69a29fc9..6cd9d0976 100644 --- a/packages/form-core/src/FieldGroupApi.ts +++ b/packages/form-core/src/FieldGroupApi.ts @@ -1,5 +1,6 @@ -import { Derived } from '@tanstack/store' +import { createStore } from '@tanstack/store' import { concatenatePaths, getBy, makePathArray } from './utils' +import type { ReadonlyStore } from '@tanstack/store' import type { Updater } from './utils' import type { FormApi, @@ -225,7 +226,7 @@ export class FieldGroupApi< return newProps } - store: Derived> + store: ReadonlyStore> get state() { return this.store.state @@ -275,38 +276,35 @@ export class FieldGroupApi< this.fieldsMap = opts.fields } - this.store = new Derived({ - deps: [this.form.store], - fn: ({ currDepVals }) => { - const currFormStore = currDepVals[0] - let values: TFieldGroupData - if (typeof this.fieldsMap === 'string') { - // all values live at that name, so we can directly fetch it - values = getBy(currFormStore.values, this.fieldsMap) - } else { - // we need to fetch the values from all places where they were mapped from - values = {} as never - const fields: Record = this - .fieldsMap as never - for (const key in fields) { - values[key] = getBy(currFormStore.values, fields[key]) - } + this.store = createStore(() => { + const currFormStore = this.form.store.get() + let values: TFieldGroupData + if (typeof this.fieldsMap === 'string') { + // all values live at that name, so we can directly fetch it + values = getBy(currFormStore.values, this.fieldsMap) + } else { + // we need to fetch the values from all places where they were mapped from + values = {} as never + const fields: Record = this + .fieldsMap as never + for (const key in fields) { + values[key] = getBy(currFormStore.values, fields[key]) } + } - return { - values, - } - }, + return { + values, + } }) } /** * Mounts the field group instance to listen to value changes. + * + * TODO: Remove */ mount = () => { - const cleanup = this.store.mount() - - return cleanup + return () => {} } /** diff --git a/packages/form-core/src/FormApi.ts b/packages/form-core/src/FormApi.ts index f1b5abde4..d78c39a8a 100644 --- a/packages/form-core/src/FormApi.ts +++ b/packages/form-core/src/FormApi.ts @@ -1,4 +1,4 @@ -import { Derived, Store, batch } from '@tanstack/store' +import { batch, createStore } from '@tanstack/store' import { deleteBy, determineFormLevelErrorSourceAndValue, @@ -21,6 +21,7 @@ import { } from './standardSchemaValidator' import { defaultFieldMeta, metaHelper } from './metaHelper' import { formEventClient } from './EventClient' +import type { ReadonlyStore, Store } from '@tanstack/store' // types import type { ValidationLogicFn } from './ValidationLogic' @@ -891,7 +892,7 @@ export class FormApi< TOnServer > > - fieldMetaDerived: Derived< + fieldMetaDerived: Store< FormState< TFormData, TOnMount, @@ -906,7 +907,7 @@ export class FormApi< TOnServer >['fieldMeta'] > - store: Derived< + store: ReadonlyStore< FormState< TFormData, TOnMount, @@ -1036,16 +1037,27 @@ export class FormApi< } } - this.baseStore = new Store(baseStoreVal) + this.baseStore = createStore(baseStoreVal) as never - this.fieldMetaDerived = new Derived({ - deps: [this.baseStore], - fn: ({ prevDepVals, currDepVals, prevVal: _prevVal }) => { - const prevVal = _prevVal as - | Record, AnyFieldMeta> - | undefined - const prevBaseStore = prevDepVals?.[0] - const currBaseStore = currDepVals[0] + let prevBaseStore: + | BaseFormState< + TFormData, + TOnMount, + TOnChange, + TOnChangeAsync, + TOnBlur, + TOnBlurAsync, + TOnSubmit, + TOnSubmitAsync, + TOnDynamic, + TOnDynamicAsync, + TOnServer + > + | undefined = undefined + + this.fieldMetaDerived = createStore( + (prevVal: Record, AnyFieldMeta> | undefined) => { + const currBaseStore = this.baseStore.get() let originalMetaCount = 0 @@ -1142,164 +1154,177 @@ export class FormApi< return prevVal } + prevBaseStore = this.baseStore.get() + return fieldMeta }, - }) + ) as never - this.store = new Derived({ - deps: [this.baseStore, this.fieldMetaDerived], - fn: ({ prevDepVals, currDepVals, prevVal: _prevVal }) => { - const prevVal = _prevVal as - | FormState< - TFormData, - TOnMount, - TOnChange, - TOnChangeAsync, - TOnBlur, - TOnBlurAsync, - TOnSubmit, - TOnSubmitAsync, - TOnDynamic, - TOnDynamicAsync, - TOnServer - > - | undefined - const prevBaseStore = prevDepVals?.[0] - const currBaseStore = currDepVals[0] - const currFieldMeta = currDepVals[1] + let prevBaseStoreForStore: + | BaseFormState< + TFormData, + TOnMount, + TOnChange, + TOnChangeAsync, + TOnBlur, + TOnBlurAsync, + TOnSubmit, + TOnSubmitAsync, + TOnDynamic, + TOnDynamicAsync, + TOnServer + > + | undefined = undefined - // Computed state - const fieldMetaValues = Object.values(currFieldMeta).filter( - Boolean, - ) as AnyFieldMeta[] + this.store = createStore< + FormState< + TFormData, + TOnMount, + TOnChange, + TOnChangeAsync, + TOnBlur, + TOnBlurAsync, + TOnSubmit, + TOnSubmitAsync, + TOnDynamic, + TOnDynamicAsync, + TOnServer + > + >((prevVal) => { + const currBaseStore = this.baseStore.get() + const currFieldMeta = this.fieldMetaDerived.get() - const isFieldsValidating = fieldMetaValues.some( - (field) => field.isValidating, - ) + // Computed state + const fieldMetaValues = Object.values(currFieldMeta).filter( + Boolean, + ) as AnyFieldMeta[] - const isFieldsValid = fieldMetaValues.every((field) => field.isValid) + const isFieldsValidating = fieldMetaValues.some( + (field) => field.isValidating, + ) - const isTouched = fieldMetaValues.some((field) => field.isTouched) - const isBlurred = fieldMetaValues.some((field) => field.isBlurred) - const isDefaultValue = fieldMetaValues.every( - (field) => field.isDefaultValue, - ) + const isFieldsValid = fieldMetaValues.every((field) => field.isValid) - const shouldInvalidateOnMount = - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - isTouched && currBaseStore.errorMap?.onMount + const isTouched = fieldMetaValues.some((field) => field.isTouched) + const isBlurred = fieldMetaValues.some((field) => field.isBlurred) + const isDefaultValue = fieldMetaValues.every( + (field) => field.isDefaultValue, + ) - const isDirty = fieldMetaValues.some((field) => field.isDirty) - const isPristine = !isDirty + const shouldInvalidateOnMount = + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + isTouched && currBaseStore.errorMap?.onMount - const hasOnMountError = Boolean( - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - currBaseStore.errorMap?.onMount || - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - fieldMetaValues.some((f) => f?.errorMap?.onMount), - ) + const isDirty = fieldMetaValues.some((field) => field.isDirty) + const isPristine = !isDirty - const isValidating = !!isFieldsValidating + const hasOnMountError = Boolean( + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + currBaseStore.errorMap?.onMount || + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + fieldMetaValues.some((f) => f?.errorMap?.onMount), + ) - // As `errors` is not a primitive, we need to aggressively persist the same referencial value for performance reasons - let errors = prevVal?.errors ?? [] - if ( - !prevBaseStore || - currBaseStore.errorMap !== prevBaseStore.errorMap - ) { - errors = Object.values(currBaseStore.errorMap).reduce< - Array< - | UnwrapFormValidateOrFn - | UnwrapFormValidateOrFn - | UnwrapFormAsyncValidateOrFn - | UnwrapFormValidateOrFn - | UnwrapFormAsyncValidateOrFn - | UnwrapFormValidateOrFn - | UnwrapFormAsyncValidateOrFn - | UnwrapFormAsyncValidateOrFn - > - >((prev, curr) => { - if (curr === undefined) return prev - - if (curr && isGlobalFormValidationError(curr)) { - prev.push(curr.form as never) - return prev - } - prev.push(curr as never) + const isValidating = !!isFieldsValidating + + // As `errors` is not a primitive, we need to aggressively persist the same referencial value for performance reasons + let errors = prevVal?.errors ?? [] + if ( + !prevBaseStoreForStore || + currBaseStore.errorMap !== prevBaseStoreForStore.errorMap + ) { + errors = Object.values(currBaseStore.errorMap).reduce< + Array< + | UnwrapFormValidateOrFn + | UnwrapFormValidateOrFn + | UnwrapFormAsyncValidateOrFn + | UnwrapFormValidateOrFn + | UnwrapFormAsyncValidateOrFn + | UnwrapFormValidateOrFn + | UnwrapFormAsyncValidateOrFn + | UnwrapFormAsyncValidateOrFn + > + >((prev, curr) => { + if (curr === undefined) return prev + + if (curr && isGlobalFormValidationError(curr)) { + prev.push(curr.form as never) return prev - }, []) - } + } + prev.push(curr as never) + return prev + }, []) + } - const isFormValid = errors.length === 0 - const isValid = isFieldsValid && isFormValid - const submitInvalid = this.options.canSubmitWhenInvalid ?? false - const canSubmit = - (currBaseStore.submissionAttempts === 0 && - !isTouched && - !hasOnMountError) || - (!isValidating && !currBaseStore.isSubmitting && isValid) || - submitInvalid - - let errorMap = currBaseStore.errorMap - if (shouldInvalidateOnMount) { - errors = errors.filter( - (err) => err !== currBaseStore.errorMap.onMount, - ) - errorMap = Object.assign(errorMap, { onMount: undefined }) - } + const isFormValid = errors.length === 0 + const isValid = isFieldsValid && isFormValid + const submitInvalid = this.options.canSubmitWhenInvalid ?? false + const canSubmit = + (currBaseStore.submissionAttempts === 0 && + !isTouched && + !hasOnMountError) || + (!isValidating && !currBaseStore.isSubmitting && isValid) || + submitInvalid + + let errorMap = currBaseStore.errorMap + if (shouldInvalidateOnMount) { + errors = errors.filter((err) => err !== currBaseStore.errorMap.onMount) + errorMap = Object.assign(errorMap, { onMount: undefined }) + } - if ( - prevVal && - prevBaseStore && - prevVal.errorMap === errorMap && - prevVal.fieldMeta === this.fieldMetaDerived.state && - prevVal.errors === errors && - prevVal.isFieldsValidating === isFieldsValidating && - prevVal.isFieldsValid === isFieldsValid && - prevVal.isFormValid === isFormValid && - prevVal.isValid === isValid && - prevVal.canSubmit === canSubmit && - prevVal.isTouched === isTouched && - prevVal.isBlurred === isBlurred && - prevVal.isPristine === isPristine && - prevVal.isDefaultValue === isDefaultValue && - prevVal.isDirty === isDirty && - evaluate(prevBaseStore, currBaseStore) - ) { - return prevVal - } + if ( + prevVal && + prevBaseStoreForStore && + prevVal.errorMap === errorMap && + prevVal.fieldMeta === this.fieldMetaDerived.state && + prevVal.errors === errors && + prevVal.isFieldsValidating === isFieldsValidating && + prevVal.isFieldsValid === isFieldsValid && + prevVal.isFormValid === isFormValid && + prevVal.isValid === isValid && + prevVal.canSubmit === canSubmit && + prevVal.isTouched === isTouched && + prevVal.isBlurred === isBlurred && + prevVal.isPristine === isPristine && + prevVal.isDefaultValue === isDefaultValue && + prevVal.isDirty === isDirty && + evaluate(prevBaseStoreForStore, currBaseStore) + ) { + return prevVal + } - const state = { - ...currBaseStore, - errorMap, - fieldMeta: this.fieldMetaDerived.state, - errors, - isFieldsValidating, - isFieldsValid, - isFormValid, - isValid, - canSubmit, - isTouched, - isBlurred, - isPristine, - isDefaultValue, - isDirty, - } as FormState< - TFormData, - TOnMount, - TOnChange, - TOnChangeAsync, - TOnBlur, - TOnBlurAsync, - TOnSubmit, - TOnSubmitAsync, - TOnDynamic, - TOnDynamicAsync, - TOnServer - > + const state = { + ...currBaseStore, + errorMap, + fieldMeta: this.fieldMetaDerived.state, + errors, + isFieldsValidating, + isFieldsValid, + isFormValid, + isValid, + canSubmit, + isTouched, + isBlurred, + isPristine, + isDefaultValue, + isDirty, + } as FormState< + TFormData, + TOnMount, + TOnChange, + TOnChangeAsync, + TOnBlur, + TOnBlurAsync, + TOnSubmit, + TOnSubmitAsync, + TOnDynamic, + TOnDynamicAsync, + TOnServer + > - return state - }, + prevBaseStoreForStore = this.baseStore.get() + + return state }) this.handleSubmit = this.handleSubmit.bind(this) @@ -1337,9 +1362,6 @@ export class FormApi< } mount = () => { - const cleanupFieldMetaDerived = this.fieldMetaDerived.mount() - const cleanupStoreDerived = this.store.mount() - // devtool broadcasts const cleanupDevtoolBroadcast = this.store.subscribe(() => { throttleFormState(this) @@ -1383,9 +1405,7 @@ export class FormApi< cleanupFormForceSubmitListener() cleanupFormResetListener() cleanupFormStateListener() - cleanupDevtoolBroadcast() - cleanupFieldMetaDerived() - cleanupStoreDerived() + cleanupDevtoolBroadcast.unsubscribe() // broadcast form unmount for devtools formEventClient.emit('form-unmounted', { diff --git a/packages/form-core/tests/FieldApi.spec.ts b/packages/form-core/tests/FieldApi.spec.ts index d4c9ce3fe..f9d099022 100644 --- a/packages/form-core/tests/FieldApi.spec.ts +++ b/packages/form-core/tests/FieldApi.spec.ts @@ -817,13 +817,13 @@ describe('field api', () => { // No async validators defined - only sync or none }) - field.mount() + const unsub = field.mount() // Track isValidating changes const isValidatingStates: boolean[] = [] - field.store.subscribe(() => { + const storeunsub = field.store.subscribe(() => { isValidatingStates.push(field.getMeta().isValidating) - }) + }).unsubscribe // Initial state expect(field.getMeta().isValidating).toBe(false) @@ -836,6 +836,8 @@ describe('field api', () => { // This prevents unnecessary re-renders expect(isValidatingStates.every((state) => state === false)).toBe(true) expect(field.getMeta().isValidating).toBe(false) + unsub() + storeunsub() }) it('should run async validation onChange', async () => { @@ -1596,8 +1598,7 @@ describe('field api', () => { name: 'name', }) - const unmount = field.mount() - unmount() + field.mount() expect(form.getFieldInfo(field.name).instance).toBeDefined() expect(form.getFieldInfo(field.name)).toBeDefined() }) diff --git a/packages/form-core/tests/fieldMeta.spec.ts b/packages/form-core/tests/fieldMeta.spec.ts index 33b4b96df..e1aa59081 100644 --- a/packages/form-core/tests/fieldMeta.spec.ts +++ b/packages/form-core/tests/fieldMeta.spec.ts @@ -141,14 +141,12 @@ describe('fieldMeta accessing', () => { name: 'name', }) - const cleanup = field.mount() + field.mount() field.setValue('test') expect(form.state.fieldMeta.name?.isTouched).toBe(true) expect(form.state.fieldMeta.name?.isDirty).toBe(true) - cleanup() - const metaAfterCleanup = form.state.fieldMeta.name expect(metaAfterCleanup).toBeDefined() diff --git a/packages/lit-form/src/tanstack-form-controller.ts b/packages/lit-form/src/tanstack-form-controller.ts index 64bbeb9e2..1ebf38c97 100644 --- a/packages/lit-form/src/tanstack-form-controller.ts +++ b/packages/lit-form/src/tanstack-form-controller.ts @@ -288,7 +288,7 @@ export class TanStackFormController< hostConnected() { this.#subscription = this.api.store.subscribe(() => { this.#host.requestUpdate() - }) + }).unsubscribe } hostDisconnected() { diff --git a/packages/react-form/package.json b/packages/react-form/package.json index eac7680ad..b9f83690c 100644 --- a/packages/react-form/package.json +++ b/packages/react-form/package.json @@ -52,7 +52,7 @@ ], "dependencies": { "@tanstack/form-core": "workspace:*", - "@tanstack/react-store": "^0.8.1" + "@tanstack/react-store": "^0.9.1" }, "devDependencies": { "@types/react": "^19.0.7", diff --git a/packages/react-form/src/useForm.tsx b/packages/react-form/src/useForm.tsx index b4ce691e6..2d75f0d77 100644 --- a/packages/react-form/src/useForm.tsx +++ b/packages/react-form/src/useForm.tsx @@ -16,7 +16,6 @@ import type { } from '@tanstack/form-core' import type { FunctionComponent, PropsWithChildren, ReactNode } from 'react' import type { FieldComponent } from './useField' -import type { NoInfer } from '@tanstack/react-store' /** * Fields that are added onto the `FormAPI` from `@tanstack/form-core` and returned from `useForm` diff --git a/packages/react-form/tests/useForm.test.tsx b/packages/react-form/tests/useForm.test.tsx index 36daaaa51..ce6733fcb 100644 --- a/packages/react-form/tests/useForm.test.tsx +++ b/packages/react-form/tests/useForm.test.tsx @@ -781,7 +781,7 @@ describe('useForm', () => { }, }) - const { values } = useStore(form.store) + const { values } = useStore(form.store, (v) => v) useEffect(() => { fn(values) diff --git a/packages/solid-form/package.json b/packages/solid-form/package.json index 006721276..bd54753ff 100644 --- a/packages/solid-form/package.json +++ b/packages/solid-form/package.json @@ -56,7 +56,7 @@ ], "dependencies": { "@tanstack/form-core": "workspace:*", - "@tanstack/solid-store": "^0.8.1" + "@tanstack/solid-store": "^0.9.1" }, "devDependencies": { "solid-js": "^1.9.9", diff --git a/packages/solid-form/tests/createForm.test.tsx b/packages/solid-form/tests/createForm.test.tsx index 1064a4d04..d4ec4526f 100644 --- a/packages/solid-form/tests/createForm.test.tsx +++ b/packages/solid-form/tests/createForm.test.tsx @@ -214,7 +214,9 @@ describe('createForm', () => { })) const [errors, setErrors] = createSignal() - onCleanup(form.store.subscribe(() => setErrors(form.state.errorMap))) + onCleanup( + form.store.subscribe(() => setErrors(form.state.errorMap)).unsubscribe, + ) return ( <> @@ -271,7 +273,9 @@ describe('createForm', () => { })) const [errors, setErrors] = createSignal() - onCleanup(form.store.subscribe(() => setErrors(form.state.errorMap))) + onCleanup( + form.store.subscribe(() => setErrors(form.state.errorMap)).unsubscribe, + ) return ( <> @@ -325,7 +329,9 @@ describe('createForm', () => { })) const [errors, setErrors] = createSignal() - onCleanup(form.store.subscribe(() => setErrors(form.state.errorMap))) + onCleanup( + form.store.subscribe(() => setErrors(form.state.errorMap)).unsubscribe, + ) return ( <> @@ -381,7 +387,9 @@ describe('createForm', () => { })) const [errors, setErrors] = createSignal() - onCleanup(form.store.subscribe(() => setErrors(form.state.errorMap))) + onCleanup( + form.store.subscribe(() => setErrors(form.state.errorMap)).unsubscribe, + ) return ( <> @@ -444,7 +452,7 @@ describe('createForm', () => { onCleanup( form.store.subscribe(() => setErrors(form.state.errorMap.onChange?.toString() || ''), - ), + ).unsubscribe, ) return ( diff --git a/packages/svelte-form/package.json b/packages/svelte-form/package.json index 7cef4b42a..c75f4a8a4 100644 --- a/packages/svelte-form/package.json +++ b/packages/svelte-form/package.json @@ -41,7 +41,7 @@ ], "dependencies": { "@tanstack/form-core": "workspace:*", - "@tanstack/svelte-store": "^0.9.1" + "@tanstack/svelte-store": "^0.10.1" }, "devDependencies": { "@sveltejs/package": "^2.5.3", diff --git a/packages/vue-form/package.json b/packages/vue-form/package.json index b6a994031..92dbad82e 100644 --- a/packages/vue-form/package.json +++ b/packages/vue-form/package.json @@ -53,7 +53,7 @@ ], "dependencies": { "@tanstack/form-core": "workspace:*", - "@tanstack/vue-store": "^0.8.1" + "@tanstack/vue-store": "^0.9.1" }, "devDependencies": { "@vitejs/plugin-vue": "^5.2.4", diff --git a/packages/vue-form/src/useForm.tsx b/packages/vue-form/src/useForm.tsx index e83e50015..74be6e56a 100644 --- a/packages/vue-form/src/useForm.tsx +++ b/packages/vue-form/src/useForm.tsx @@ -8,7 +8,6 @@ import type { FormState, FormValidateOrFn, } from '@tanstack/form-core' -import type { NoInfer } from '@tanstack/vue-store' import type { ComponentOptionsMixin, CreateComponentPublicInstanceWithMixins, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 498efb24a..ad5def511 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -581,8 +581,8 @@ importers: specifier: ^1.28.3 version: link:../../../packages/react-form-nextjs '@tanstack/react-store': - specifier: ^0.8.1 - version: 0.8.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^0.9.1 + version: 0.9.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next: specifier: 16.0.5 version: 16.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0) @@ -612,8 +612,8 @@ importers: specifier: ^1.28.3 version: link:../../../packages/react-form-nextjs '@tanstack/react-store': - specifier: ^0.8.1 - version: 0.8.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^0.9.1 + version: 0.9.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next: specifier: 16.0.5 version: 16.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0) @@ -689,8 +689,8 @@ importers: specifier: ^1.28.3 version: link:../../../packages/react-form-remix '@tanstack/react-store': - specifier: ^0.8.1 - version: 0.8.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^0.9.1 + version: 0.9.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) isbot: specifier: ^5.1.30 version: 5.1.31 @@ -812,8 +812,8 @@ importers: specifier: ^1.134.9 version: 1.135.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.2.2(@types/node@24.1.0)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(sugarss@5.0.1(postcss@8.5.6))(terser@5.43.1)(tsx@4.19.4)(yaml@2.8.1)))(vite@7.2.2(@types/node@24.1.0)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(sugarss@5.0.1(postcss@8.5.6))(terser@5.43.1)(tsx@4.19.4)(yaml@2.8.1))(webpack@5.101.2(@swc/core@1.13.5)) '@tanstack/react-store': - specifier: ^0.8.1 - version: 0.8.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^0.9.1 + version: 0.9.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: specifier: ^19.0.0 version: 19.1.0 @@ -1213,8 +1213,8 @@ importers: packages/angular-form: dependencies: '@tanstack/angular-store': - specifier: ^0.8.1 - version: 0.8.1(@angular/common@20.3.6(@angular/core@20.3.6(@angular/compiler@20.3.6)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.6(@angular/compiler@20.3.6)(rxjs@7.8.2)(zone.js@0.15.1)) + specifier: ^0.9.1 + version: 0.9.1(@angular/common@20.3.6(@angular/core@20.3.6(@angular/compiler@20.3.6)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.6(@angular/compiler@20.3.6)(rxjs@7.8.2)(zone.js@0.15.1)) '@tanstack/form-core': specifier: workspace:* version: link:../form-core @@ -1271,8 +1271,8 @@ importers: specifier: ^0.1.1 version: 0.1.1 '@tanstack/store': - specifier: ^0.8.1 - version: 0.8.1 + specifier: ^0.9.1 + version: 0.9.1 devDependencies: arktype: specifier: ^2.1.22 @@ -1331,8 +1331,8 @@ importers: specifier: workspace:* version: link:../form-core '@tanstack/react-store': - specifier: ^0.8.1 - version: 0.8.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^0.9.1 + version: 0.9.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) devDependencies: '@types/react': specifier: ^19.0.7 @@ -1480,8 +1480,8 @@ importers: specifier: workspace:* version: link:../form-core '@tanstack/solid-store': - specifier: ^0.8.1 - version: 0.8.1(solid-js@1.9.9) + specifier: ^0.9.1 + version: 0.9.1(solid-js@1.9.9) devDependencies: solid-js: specifier: ^1.9.9 @@ -1515,8 +1515,8 @@ importers: specifier: workspace:* version: link:../form-core '@tanstack/svelte-store': - specifier: ^0.9.1 - version: 0.9.1(svelte@5.41.1) + specifier: ^0.10.1 + version: 0.10.1(svelte@5.41.1) devDependencies: '@sveltejs/package': specifier: ^2.5.3 @@ -1540,8 +1540,8 @@ importers: specifier: workspace:* version: link:../form-core '@tanstack/vue-store': - specifier: ^0.8.1 - version: 0.8.1(vue@3.5.16(typescript@5.9.3)) + specifier: ^0.9.1 + version: 0.9.1(vue@3.5.16(typescript@5.9.3)) devDependencies: '@vitejs/plugin-vue': specifier: ^5.2.4 @@ -4773,8 +4773,8 @@ packages: '@swc/types@0.1.24': resolution: {integrity: sha512-tjTMh3V4vAORHtdTprLlfoMptu1WfTZG9Rsca6yOKyNYsRr+MUXutKmliB17orgSZk5DpnDxs8GUdd/qwYxOng==} - '@tanstack/angular-store@0.8.1': - resolution: {integrity: sha512-Zb8e1QVeBoSu/s1R3fXczctEqB7lZrdPL87/9INwCaRSY3jPqNn3SlzP8yvwvBwv7axaFgfUrhQJXlnACC3Vnw==} + '@tanstack/angular-store@0.9.1': + resolution: {integrity: sha512-XdrVBZperSRulkk8kLsPP/apNZQZwAWvNeO6PMb+kRv7iOXAzxaIK2LQTZFLtfT1QgQZFeEqU8klJcdcuG6JcQ==} peerDependencies: '@angular/common': '>=19.0.0' '@angular/core': '>=19.0.0' @@ -4914,6 +4914,12 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@tanstack/react-store@0.9.1': + resolution: {integrity: sha512-YzJLnRvy5lIEFTLWBAZmcOjK3+2AepnBv/sr6NZmiqJvq7zTQggyK99Gw8fqYdMdHPQWXjz0epFKJXC+9V2xDA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@tanstack/router-core@1.135.2': resolution: {integrity: sha512-fhJSGmbqE78Ou6e+cnJ9exmjCzCZ9IXT2rApiPAgeItKj2yy1qmTEoR11n0x0fiNkkBxHL1us+QyG8JfNELiQA==} engines: {node: '>=12'} @@ -4957,8 +4963,8 @@ packages: peerDependencies: solid-js: '>=1.9.7' - '@tanstack/solid-store@0.8.1': - resolution: {integrity: sha512-1p4TTJGIZJ2J7130aTo7oWfHVRSCd9DxLP3HzcDMnzn56pz8krlyBEzsE+z/sHGXP0EC/JjT02fgj2L9+fmf8Q==} + '@tanstack/solid-store@0.9.1': + resolution: {integrity: sha512-gx7ToM+Yrkui36NIj0HjAufzv1Dg8usjtVFy5H3Ll52Xjuz+eliIJL+ihAr4LRuWh3nDPBR+nCLW0ShFrbE5yw==} peerDependencies: solid-js: ^1.6.0 @@ -4983,8 +4989,11 @@ packages: '@tanstack/store@0.8.1': resolution: {integrity: sha512-PtOisLjUZPz5VyPRSCGjNOlwTvabdTBQ2K80DpVL1chGVr35WRxfeavAPdNq6pm/t7F8GhoR2qtmkkqtCEtHYw==} - '@tanstack/svelte-store@0.9.1': - resolution: {integrity: sha512-4RYp0CXSB9tjlUZNl29mjraWeRquKzuaW+bGGI4s3kS6BWatgt7BfX4OtoLT8MTBdepW9ARwqHZ3s8YGpfOZkQ==} + '@tanstack/store@0.9.1': + resolution: {integrity: sha512-+qcNkOy0N1qSGsP7omVCW0SDrXtaDcycPqBDE726yryiA5eTDFpjBReaYjghVJwNf1pcPMyzIwTGlYjCSQR0Fg==} + + '@tanstack/svelte-store@0.10.1': + resolution: {integrity: sha512-heeyV9bZQHbEJyJ7oWegQXmcyA8NSPP58JsZgRpvf8+lwEMfX+MW1IvPJbGZqmH+poULAz7DDxjC4JEe7l57LA==} peerDependencies: svelte: ^5.0.0 @@ -5000,8 +5009,8 @@ packages: resolution: {integrity: sha512-FOl8EF6SAcljanKSm5aBeJaflFcxQAytTbxtNW8HC6D4x+UBW68IC4tBcrlrsI0wXHBmC/Gz4Ovvv8qCtiXSgQ==} engines: {node: '>=18'} - '@tanstack/vue-store@0.8.1': - resolution: {integrity: sha512-37rNptEo86+2Jm2kTLvgVtboRRwHkksjxCKCrdl73eeZIU0jU34ZMP/ayd5+bCCo6epdbrqcb13gjUBSGp4Blg==} + '@tanstack/vue-store@0.9.1': + resolution: {integrity: sha512-mXXZzPWom656MExX2gG1fqopJhToDbqGEl98WtJ5/hyouQHtQXiAgtsPNLzUcVcwU9okM/OCWv7QAgXf6C5ziQ==} peerDependencies: '@vue/composition-api': ^1.2.1 vue: ^2.5.0 || ^3.0.0 @@ -14258,11 +14267,11 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tanstack/angular-store@0.8.1(@angular/common@20.3.6(@angular/core@20.3.6(@angular/compiler@20.3.6)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.6(@angular/compiler@20.3.6)(rxjs@7.8.2)(zone.js@0.15.1))': + '@tanstack/angular-store@0.9.1(@angular/common@20.3.6(@angular/core@20.3.6(@angular/compiler@20.3.6)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.6(@angular/compiler@20.3.6)(rxjs@7.8.2)(zone.js@0.15.1))': dependencies: '@angular/common': 20.3.6(@angular/core@20.3.6(@angular/compiler@20.3.6)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) '@angular/core': 20.3.6(@angular/compiler@20.3.6)(rxjs@7.8.2)(zone.js@0.15.1) - '@tanstack/store': 0.8.1 + '@tanstack/store': 0.9.1 tslib: 2.8.1 '@tanstack/devtools-client@0.0.3': @@ -14460,6 +14469,13 @@ snapshots: react-dom: 19.1.0(react@19.1.0) use-sync-external-store: 1.6.0(react@19.1.0) + '@tanstack/react-store@0.9.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/store': 0.9.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + use-sync-external-store: 1.6.0(react@19.1.0) + '@tanstack/router-core@1.135.2': dependencies: '@tanstack/history': 1.133.28 @@ -14545,9 +14561,9 @@ snapshots: - csstype - utf-8-validate - '@tanstack/solid-store@0.8.1(solid-js@1.9.9)': + '@tanstack/solid-store@0.9.1(solid-js@1.9.9)': dependencies: - '@tanstack/store': 0.8.1 + '@tanstack/store': 0.9.1 solid-js: 1.9.9 '@tanstack/start-client-core@1.135.2': @@ -14608,9 +14624,11 @@ snapshots: '@tanstack/store@0.8.1': {} - '@tanstack/svelte-store@0.9.1(svelte@5.41.1)': + '@tanstack/store@0.9.1': {} + + '@tanstack/svelte-store@0.10.1(svelte@5.41.1)': dependencies: - '@tanstack/store': 0.8.1 + '@tanstack/store': 0.9.1 svelte: 5.41.1 '@tanstack/typedoc-config@0.3.1(typescript@5.8.2)': @@ -14636,9 +14654,9 @@ snapshots: - typescript - vite - '@tanstack/vue-store@0.8.1(vue@3.5.16(typescript@5.9.3))': + '@tanstack/vue-store@0.9.1(vue@3.5.16(typescript@5.9.3))': dependencies: - '@tanstack/store': 0.8.1 + '@tanstack/store': 0.9.1 vue: 3.5.16(typescript@5.9.3) vue-demi: 0.14.10(vue@3.5.16(typescript@5.9.3))