zustand: zustand 4.1.2 create() initializer type problems with store mutators

I have a function that extracts out the initializer for a store to a function so that I can create many variants of a particular store, in this case various cache stores.

I was previously working with zustand 4.1.1 and the only type errors that I had were the getter and setter possibly being undefined, so I could suppress them using assertions.

After updating to zustand 4.1.2, there are now new type errors that prevent building, and I cannot for the life of me figure out what’s going on with them.

I made a minimal CodeSandbox demo of the problem available here.

A possibly notable observation is that without the M parameter, the type of set is reported to be (partial: Cache<K, V> | Partial<Cache<K, V>> | ((state: Cache<K, V>) => Cache<K, V> | Partial<Cache<K, V>>), replace?: boolean | undefined) => void.

With the M parameter, it instead becomes Get<Mutate<StoreApi<Cache<K, V>>, M>, "setState", undefined>.

The closest I’ve gotten to figuring out a possible cause is that the Get type might not be inferring that the "setState" and "getState" fields should (I think) be guaranteed to be present, but that’s really just grasping at straws.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 18 (12 by maintainers)

Most upvoted comments

You want create()() for TS:

export const vanillaStore = createVanilla<MyStore>()(
  persist((set, get) => ({
    // ...
  }))
)

export const useBoundStore = <T>(selector: (state: MyStore) => T) => useStore(vanillaStore, selector)

You need to use persist for vanilla store.

btw, I’m planning to deprecate create(vanillaStore) usage in the future. Because unlike v3, it’s easy to do in userland with v4 api.

import createStore from 'zustand/vanilla'
import { useStore } from 'zustand'
import { persist } from 'zustand/middleware'

const store = createStore(persist(() => ({ ... })))
const { getState, setState, subscribe, destroy } = store

const useBoundStore = (selector) => useStore(store, selector)

When I tried some experiments before v4, I thought it would be nice to freely mutate store (especially setState), so option 1 would be taken. However, reading this thread and the fact that all our middlewares in this repo mutate stores as subtypes, I now think option 2 is better even if it comes with a little lie. middlewares should follow the keep subtype rule.

Yeah, I kinda agree. The good part is there are still no restrictions, users can use middlewares that don’t preserve the subtype relationship and they’d still seamlessly work, it’s just that we’re making a little assumption to improve types of StateCreator when the mutator is generic.

I’m confused, which PR is option 1 and which is option 2?

Oops, I made mistakes in the descriptions of the PRs haha. #1371 is option 2 and #1372 is option 1.