diff --git a/docs/reference/classes/store.md b/docs/reference/classes/store.md index aa26ee9..d8e88c8 100644 --- a/docs/reference/classes/store.md +++ b/docs/reference/classes/store.md @@ -13,8 +13,6 @@ Defined in: [store.ts:28](https://github.com/TanStack/store/blob/main/packages/s • **TState** -• **TUpdater** *extends* `AnyUpdater` = (`cb`) => `TState` - ## Constructors ### new Store() diff --git a/docs/reference/interfaces/storeoptions.md b/docs/reference/interfaces/storeoptions.md index 1270888..5b0e909 100644 --- a/docs/reference/interfaces/storeoptions.md +++ b/docs/reference/interfaces/storeoptions.md @@ -13,8 +13,6 @@ Defined in: [store.ts:5](https://github.com/TanStack/store/blob/main/packages/st • **TState** -• **TUpdater** *extends* `AnyUpdater` = (`cb`) => `TState` - ## Properties ### onSubscribe()? diff --git a/examples/react/simple/src/index.tsx b/examples/react/simple/src/index.tsx index cbf8356..2bb3cda 100644 --- a/examples/react/simple/src/index.tsx +++ b/examples/react/simple/src/index.tsx @@ -8,6 +8,12 @@ export const store = new Store({ cats: 0, }) +const countStore = new Store(1, { + updateFn: prevState => updater => { + return updater(prevState) + prevState; + } +}); + interface DisplayProps { animal: 'dogs' | 'cats' } @@ -35,6 +41,17 @@ const Increment = ({ animal }: IncrementProps) => ( ) +const DisplayCount = () => { + const count = useStore(countStore); + return ( + <> + + +
{`count: ${count}`}
+ + ); +}; + function App() { return (
@@ -47,6 +64,7 @@ function App() { +
) } diff --git a/packages/angular-store/src/index.ts b/packages/angular-store/src/index.ts index af0ad0c..3fefbc8 100644 --- a/packages/angular-store/src/index.ts +++ b/packages/angular-store/src/index.ts @@ -17,7 +17,7 @@ export * from '@tanstack/store' type NoInfer = [T][T extends any ? 0 : never] export function injectStore>( - store: Store, + store: Store, selector?: (state: NoInfer) => TSelected, options?: CreateSignalOptions & { injector?: Injector }, ): Signal @@ -27,7 +27,7 @@ export function injectStore>( options?: CreateSignalOptions & { injector?: Injector }, ): Signal export function injectStore>( - store: Store | Derived, + store: Store | Derived, selector: (state: NoInfer) => TSelected = (d) => d as TSelected, options: CreateSignalOptions & { injector?: Injector } = { equal: shallow, diff --git a/packages/react-store/src/index.ts b/packages/react-store/src/index.ts index 155b0f5..e67d8da 100644 --- a/packages/react-store/src/index.ts +++ b/packages/react-store/src/index.ts @@ -9,7 +9,7 @@ export * from '@tanstack/store' export type NoInfer = [T][T extends any ? 0 : never] export function useStore>( - store: Store, + store: Store, selector?: (state: NoInfer) => TSelected, ): TSelected export function useStore>( @@ -17,7 +17,7 @@ export function useStore>( selector?: (state: NoInfer) => TSelected, ): TSelected export function useStore>( - store: Store | Derived, + store: Store | Derived, selector: (state: NoInfer) => TSelected = (d) => d as any, ): TSelected { const slice = useSyncExternalStoreWithSelector( diff --git a/packages/solid-store/src/index.tsx b/packages/solid-store/src/index.tsx index 3897e53..03bf45c 100644 --- a/packages/solid-store/src/index.tsx +++ b/packages/solid-store/src/index.tsx @@ -11,7 +11,7 @@ export * from '@tanstack/store' export type NoInfer = [T][T extends any ? 0 : never] export function useStore>( - store: Store, + store: Store, selector?: (state: NoInfer) => TSelected, ): Accessor export function useStore>( @@ -19,7 +19,7 @@ export function useStore>( selector?: (state: NoInfer) => TSelected, ): Accessor export function useStore>( - store: Store | Derived, + store: Store | Derived, selector: (state: NoInfer) => TSelected = (d) => d as any, ): Accessor { const [slice, setSlice] = createStore({ diff --git a/packages/store/src/store.ts b/packages/store/src/store.ts index 94e9fc5..6cc2e26 100644 --- a/packages/store/src/store.ts +++ b/packages/store/src/store.ts @@ -1,15 +1,12 @@ import { __flush } from './scheduler' import { isUpdaterFunction } from './types' -import type { AnyUpdater, Listener, Updater } from './types' +import type { Listener } from './types' -export interface StoreOptions< - TState, - TUpdater extends AnyUpdater = (cb: TState) => TState, -> { +export interface StoreOptions< TState> { /** * Replace the default update function with a custom one. */ - updateFn?: (previous: TState) => (updater: TUpdater) => TState + updateFn?: (previous: TState) => (updater: (prev: TState) => TState) => TState /** * Called when a listener subscribes to the store. * @@ -17,7 +14,7 @@ export interface StoreOptions< */ onSubscribe?: ( listener: Listener, - store: Store, + store: Store, ) => () => void /** * Called after the state has been updated, used to derive other state. @@ -25,16 +22,13 @@ export interface StoreOptions< onUpdate?: () => void } -export class Store< - TState, - TUpdater extends AnyUpdater = (cb: TState) => TState, -> { +export class Store { listeners = new Set>() state: TState prevState: TState - options?: StoreOptions + options?: StoreOptions - constructor(initialState: TState, options?: StoreOptions) { + constructor(initialState: TState, options?: StoreOptions) { this.prevState = initialState this.state = initialState this.options = options @@ -42,30 +36,23 @@ export class Store< subscribe = (listener: Listener) => { this.listeners.add(listener) - const unsub = this.options?.onSubscribe?.(listener, this) + const unsubscribe = this.options?.onSubscribe?.(listener, this) return () => { this.listeners.delete(listener) - unsub?.() + unsubscribe?.() } } /** * Update the store state safely with improved type checking */ - setState(updater: (prevState: TState) => TState): void - setState(updater: TState): void - setState(updater: TUpdater): void - setState(updater: Updater | TUpdater): void { + setState(updater: TState | ((prevState: TState) => TState)): void { this.prevState = this.state - if (this.options?.updateFn) { - this.state = this.options.updateFn(this.prevState)(updater as TUpdater) + if (isUpdaterFunction(updater)) { + this.state = this.options?.updateFn ? this.options.updateFn(this.prevState)(updater) : updater(this.prevState) } else { - if (isUpdaterFunction(updater)) { - this.state = updater(this.prevState) - } else { - this.state = updater as TState - } + this.state = this.options?.updateFn ? this.options.updateFn(this.prevState)(() => updater) : updater } // Always run onUpdate, regardless of batching diff --git a/packages/store/src/types.ts b/packages/store/src/types.ts index a869cdd..be31563 100644 --- a/packages/store/src/types.ts +++ b/packages/store/src/types.ts @@ -1,8 +1,3 @@ -/** - * @private - */ -export type AnyUpdater = (prev: any) => any - /** * Type-safe updater that can be either a function or direct value */ diff --git a/packages/store/tests/scheduler.test.ts b/packages/store/tests/scheduler.test.ts index c09f564..70631b0 100644 --- a/packages/store/tests/scheduler.test.ts +++ b/packages/store/tests/scheduler.test.ts @@ -9,7 +9,7 @@ import { describe('Scheduler logic', () => { test('Should build a graph properly', () => { - const count = new Store(10) + const count = new Store(10) const halfCount = new Derived({ deps: [count], @@ -36,7 +36,7 @@ describe('Scheduler logic', () => { }) test('should unbuild a graph properly', () => { - const count = new Store(10) + const count = new Store(10) const halfCount = new Derived({ deps: [count], @@ -99,7 +99,7 @@ describe('Scheduler logic', () => { }) test('should register graph items in the wrong order properly', () => { - const count = new Store(12) + const count = new Store(12) const double = new Derived({ deps: [count], @@ -124,7 +124,7 @@ describe('Scheduler logic', () => { }) test('should register graph items in the right direction order', () => { - const count = new Store(12) + const count = new Store(12) const double = new Derived({ deps: [count], diff --git a/packages/store/tests/store.test.ts b/packages/store/tests/store.test.ts index 5d7b9ae..26db589 100644 --- a/packages/store/tests/store.test.ts +++ b/packages/store/tests/store.test.ts @@ -53,6 +53,24 @@ describe('store', () => { expect(typeof store.state).toEqual('number') }) + test(`updateFn acts as state transformer when directly updating state`, () => { + const store = new Store(1, { + updateFn: prevState => updater => { + return updater(prevState) + prevState; + } + }) + + store.setState(1) + + expect(store.state).toEqual(2) + + store.setState(1) + + expect(store.state).toEqual(3) + + expect(typeof store.state).toEqual('number') + }) + test('listeners should receive old and new values', () => { const store = new Store(12) const fn = vi.fn() diff --git a/packages/svelte-store/src/index.svelte.ts b/packages/svelte-store/src/index.svelte.ts index 3d8429a..2626b7f 100644 --- a/packages/svelte-store/src/index.svelte.ts +++ b/packages/svelte-store/src/index.svelte.ts @@ -8,7 +8,7 @@ export * from '@tanstack/store' export type NoInfer = [T][T extends any ? 0 : never] export function useStore>( - store: Store, + store: Store, selector?: (state: NoInfer) => TSelected, ): { readonly current: TSelected } export function useStore>( @@ -16,7 +16,7 @@ export function useStore>( selector?: (state: NoInfer) => TSelected, ): { readonly current: TSelected } export function useStore>( - store: Store | Derived, + store: Store | Derived, selector: (state: NoInfer) => TSelected = (d) => d as any, ): { readonly current: TSelected } { let slice = $state(selector(store.state)) diff --git a/packages/vue-store/src/index.ts b/packages/vue-store/src/index.ts index 0d3dfaa..ac494e2 100644 --- a/packages/vue-store/src/index.ts +++ b/packages/vue-store/src/index.ts @@ -10,7 +10,7 @@ export * from '@tanstack/store' export type NoInfer = [T][T extends any ? 0 : never] export function useStore>( - store: Store, + store: Store, selector?: (state: NoInfer) => TSelected, ): Readonly> export function useStore>( @@ -18,7 +18,7 @@ export function useStore>( selector?: (state: NoInfer) => TSelected, ): Readonly> export function useStore>( - store: Store | Derived, + store: Store | Derived, selector: (state: NoInfer) => TSelected = (d) => d as any, ): Readonly> { const slice = ref(selector(store.state)) as Ref