Skip to content

Commit

Permalink
fix(types): fix StateCreator subtyping
Browse files Browse the repository at this point in the history
  • Loading branch information
devanshj committed Oct 22, 2022
1 parent d27ea94 commit 66f61ac
Show file tree
Hide file tree
Showing 8 changed files with 36 additions and 69 deletions.
20 changes: 4 additions & 16 deletions docs/guides/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,9 @@ type Logger = <
) => StateCreator<T, Mps, Mcs>

type LoggerImpl = <T extends State>(
f: PopArgument<StateCreator<T, [], []>>,
f: StateCreator<T, [], []>,
name?: string
) => PopArgument<StateCreator<T, [], []>>
) => StateCreator<T, [], []>

const loggerImpl: LoggerImpl = (f, name) => (set, get, store) => {
type T = ReturnType<typeof f>
Expand All @@ -263,12 +263,6 @@ const loggerImpl: LoggerImpl = (f, name) => (set, get, store) => {

export const logger = loggerImpl as unknown as Logger

type PopArgument<T extends (...a: never[]) => unknown> = T extends (
...a: [...infer A, infer _]
) => infer R
? (...a: A) => R
: never

// ---

const useBearStore = create<BearState>()(
Expand Down Expand Up @@ -310,9 +304,9 @@ declare module 'zustand' {
}

type FooImpl = <T extends State, A>(
f: PopArgument<StateCreator<T, [], []>>,
f: StateCreator<T, [], []>,
bar: A
) => PopArgument<StateCreator<T, [], []>>
) => StateCreator<T, [], []>

const fooImpl: FooImpl = (f, bar) => (set, get, _store) => {
type T = ReturnType<typeof f>
Expand All @@ -325,12 +319,6 @@ const fooImpl: FooImpl = (f, bar) => (set, get, _store) => {

export const foo = fooImpl as unknown as Foo

type PopArgument<T extends (...a: never[]) => unknown> = T extends (
...a: [...infer A, infer _]
) => infer R
? (...a: A) => R
: never

type Write<T extends object, U extends object> = Omit<T, keyof U> & U

type Cast<T, U> = T extends U ? T : U
Expand Down
10 changes: 2 additions & 8 deletions src/middleware/devtools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,9 @@ declare module '../vanilla' {
}

type DevtoolsImpl = <T>(
storeInitializer: PopArgument<StateCreator<T, [], []>>,
storeInitializer: StateCreator<T, [], []>,
devtoolsOptions?: DevtoolsOptions
) => PopArgument<StateCreator<T, [], []>>

type PopArgument<T extends (...a: never[]) => unknown> = T extends (
...a: [...infer A, infer _]
) => infer R
? (...a: A) => R
: never
) => StateCreator<T, [], []>

export type NamedSet<T> = WithDevtools<StoreApi<T>>['setState']

Expand Down
10 changes: 2 additions & 8 deletions src/middleware/immer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,9 @@ type StoreImmer<S> = S extends {
: never
: never

type PopArgument<T extends (...a: never[]) => unknown> = T extends (
...a: [...infer A, infer _]
) => infer R
? (...a: A) => R
: never

type ImmerImpl = <T>(
storeInitializer: PopArgument<StateCreator<T, [], []>>
) => PopArgument<StateCreator<T, [], []>>
storeInitializer: StateCreator<T, [], []>
) => StateCreator<T, [], []>

const immerImpl: ImmerImpl = (initializer) => (set, get, store) => {
type T = ReturnType<typeof initializer>
Expand Down
10 changes: 2 additions & 8 deletions src/middleware/persist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,14 +319,8 @@ type WithPersist<S, A> = S extends { getState: () => infer T }
: never

type PersistImpl = <T>(
storeInitializer: PopArgument<StateCreator<T, [], []>>,
storeInitializer: StateCreator<T, [], []>,
options: PersistOptions<T, T>
) => PopArgument<StateCreator<T, [], []>>

type PopArgument<T extends (...a: never[]) => unknown> = T extends (
...a: [...infer A, infer _]
) => infer R
? (...a: A) => R
: never
) => StateCreator<T, [], []>

export const persist = persistImpl as unknown as Persist
10 changes: 2 additions & 8 deletions src/middleware/redux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,10 @@ declare module '../vanilla' {
}
}

type PopArgument<T extends (...a: never[]) => unknown> = T extends (
...a: [...infer A, infer _]
) => infer R
? (...a: A) => R
: never

type ReduxImpl = <T, A extends Action>(
reducer: (state: T, action: A) => T,
initialState: T
) => PopArgument<StateCreator<T & ReduxState<A>, [], []>>
) => StateCreator<T & ReduxState<A>, [], []>

const reduxImpl: ReduxImpl = (reducer, initial) => (set, _get, api) => {
type S = typeof initial
Expand All @@ -53,4 +47,4 @@ const reduxImpl: ReduxImpl = (reducer, initial) => (set, _get, api) => {

return { dispatch: (...a) => (api as any).dispatch(...a), ...initial }
}
export const redux = reduxImpl as Redux
export const redux = reduxImpl as unknown as Redux
10 changes: 2 additions & 8 deletions src/middleware/subscribeWithSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,8 @@ type StoreSubscribeWithSelector<T> = {
}

type SubscribeWithSelectorImpl = <T extends object>(
storeInitializer: PopArgument<StateCreator<T, [], []>>
) => PopArgument<StateCreator<T, [], []>>

type PopArgument<T extends (...a: never[]) => unknown> = T extends (
...a: [...infer A, infer _]
) => infer R
? (...a: A) => R
: never
storeInitializer: StateCreator<T, [], []>
) => StateCreator<T, [], []>

const subscribeWithSelectorImpl: SubscribeWithSelectorImpl =
(fn) => (set, get, api) => {
Expand Down
15 changes: 2 additions & 13 deletions src/vanilla.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ export type StateCreator<
> = ((
setState: Get<Mutate<StoreApi<T>, Mis>, 'setState', undefined>,
getState: Get<Mutate<StoreApi<T>, Mis>, 'getState', undefined>,
store: Mutate<StoreApi<T>, Mis>,
$$storeMutations: Mis
store: Mutate<StoreApi<T>, Mis>
) => U) & { $$storeMutators?: Mos }

// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-interface
Expand All @@ -55,12 +54,6 @@ type CreateStoreImpl = <
initializer: StateCreator<T, [], Mos>
) => Mutate<StoreApi<T>, Mos>

type PopArgument<T extends (...a: never[]) => unknown> = T extends (
...a: [...infer A, infer _]
) => infer R
? (...a: A) => R
: never

const createStoreImpl: CreateStoreImpl = (createState) => {
type TState = ReturnType<typeof createState>
type Listener = (state: TState, prevState: TState) => void
Expand Down Expand Up @@ -94,11 +87,7 @@ const createStoreImpl: CreateStoreImpl = (createState) => {

const destroy: () => void = () => listeners.clear()
const api = { setState, getState, subscribe, destroy }
state = (createState as PopArgument<typeof createState>)(
setState,
getState,
api
)
state = createState(setState, getState, api)
return api as any
}

Expand Down
20 changes: 20 additions & 0 deletions tests/types.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import create, { StateCreator, StoreApi, UseBoundStore } from 'zustand'
import { persist } from 'zustand/middleware'

it('can use exposed types', () => {
type ExampleState = {
Expand Down Expand Up @@ -188,3 +189,22 @@ it('state is covariant', () => {
baz: string
}> = store
})

it('StateCreator subtyping', () => {
interface State {
count: number
increment: () => void
}

const foo: () => StateCreator<State, []> = () => (set, get) => ({
count: 0,
increment: () => {
set({ count: get().count + 1 })
},
})

create<State>()(persist(foo()))

const _testSubtyping: StateCreator<State, [['zustand/persist', unknown]]> =
{} as StateCreator<State, []>
})

0 comments on commit 66f61ac

Please sign in to comment.