Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(typescript): clean up bounded useStore recipe #1581

Merged
merged 4 commits into from
Jan 29, 2023
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 29 additions & 64 deletions docs/guides/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,91 +399,56 @@ A detailed explanation on the slices pattern can be found [here](./slices-patter

If you have some middlewares then replace `StateCreator<MyState, [], [], MySlice>` with `StateCreator<MyState, Mutators, [], MySlice>`. For example, if you are using `devtools` then it will be `StateCreator<MyState, [["zustand/devtools", never]], [], MySlice>`. See the ["Middlewares and their mutators reference"](#middlewares-and-their-mutators-reference) section for a list of all mutators.

### Using a vanilla store as a bound store

Create your vanilla store:
### A bounded `useStore` hook

```ts
import { createStore } from 'zustand/vanilla'
import { create, useStore } from 'zustand'

interface BearState {
bears: number
increase: (by: number) => void
}

export const initialBearState = { bears: 0 }
export const vanillaBearStore = createStore<BearState>((set, getState) => ({
...initialBearState,
const bearStore = create<BearState>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
```

Create a hook to provide a bound store to be used in your component:

```ts
import { useStore } from 'zustand'

export function useBoundBearStore(): BearState
export function useBoundBearStore<T>(
selector: (state: BearState) => T,
equals?: (a: T, b: T) => boolean
): T
export function useBoundBearStore(selector?: any, equals?: any) {
return useStore(vanillaBearStore, selector, equals)
function useBearStore(): BearState
function useBearStore<T>(selector: (state: BearState) => T, equals?: (a: T, b: T) => boolean): T
function useBearStore<T>(selector?: (state: BearState) => T, equals?: (a: T, b: T) => boolean) {
return useStore(bearStore, selector!, equals)
Copy link
Contributor Author

@devanshj devanshj Jan 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need the ! after selector because our types are incorrect, they don't allow writing useStore({} as StoreApi<unknown>, undefined, () => true). I had told @dai-shi about this in #725 but he said that passing undefined is an incorrect usage. I personally think in most cases types should remain true to the implementation otherwise problems like this pop up.

I mean it's not a big deal but still in case if you change your mind and want to make our types more correct then let me know I'll open a PR (we just need to add a ? after selector in out useStore type).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I still think this is good. (But, I may change my mind in the future. In that case, I think I know what to do.)

}
```

> **_NOTE:_** We prefer function overloading here, as this closely follows the definition of `useStore` itself.
> If you are not familiar with this pattern, just have a look here: [Typescript Docs](https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads)

Now you can access your vanilla store (e.g. in your tests) like:

You can also make an abstract `createBoundedUseStore` if you create bounded `useStore`s often and want to DRY things up...

```ts
import { vanillaBearStore, initialBearState } from './BearStore'

describe('MyComponent should', () => {
// remember to reset the store
beforeEach(() => {
vanillaBearStore.setState(initialBearState)
})

it('set the value', () => {
const store = vanillaBearStore
// do the test
expect(store.getState().bears).toEqual(0)
})
})
```

And access the store in your component
import { create, useStore, StoreApi } from 'zustand'

```tsx
import { useBoundBearStore } from './BearStore'

export const BearComponent = () => {
const bears = useBoundBearStore((state) => state.bears)

return <div>{bears}</div>
interface BearState {
bears: number
increase: (by: number) => void
}
```

If you want to use middlewares with your store:

```ts
import { createStore } from 'zustand/vanilla'
import { devtools } from 'zustand/middleware'
const bearStore = create<BearState>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))

export const vanillaBearStore = createStore<BearState>()(
devtools((set, getState) => ({
...initialBearState,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
)
```
const createBoundedUseStore =
((store) => (selector, equals) => useStore(store, selector as any, equals)) as
<S extends StoreApi<unknown>>(store: S) => {
(): ExtractState<S>
<T>(selector?: (state: ExtractState<S>) => T, equals?: (a: T, b: T) => boolean): T
}

For more information about why there are extra parentheses,
please see the [Basic usage section](#basic-usage).
type ExtractState<S> =
S extends { get: () => infer X } ? X : never

const useBearStore = createBoundedUseStore(bearStore)
```

## Middlewares and their mutators reference

- `devtools` — `["zustand/devtools", never]`
Expand Down