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

Support Action Name in Typescript SetState #980

Closed
WaleedZ90 opened this issue May 31, 2022 · 22 comments
Closed

Support Action Name in Typescript SetState #980

WaleedZ90 opened this issue May 31, 2022 · 22 comments

Comments

@WaleedZ90
Copy link

It came to our attention that the SetStateType action name is not defined in zustand/src/vanilla.d.ts.
Even though it's written in the documentation

Suggestion to add the definition of the actionName to the type

@dai-shi
Copy link
Member

dai-shi commented May 31, 2022

Vanilla store doesn't support action name. It's only available when you use devtools middleware. Types work magically when you add the middleware. Please also refer tests/middlewareTypes.test.tsx for working examples. There might be a bug, so if you seem to find one, please provide a reproduction. Thanks.

@WaleedZ90
Copy link
Author

As a developer I want to assign actions names to the actions I've defined in my store.
In typescript I see that the type SetState is defined in zustand/src/vanilla.d.ts

Here is the definition:
export declare type SetState<T extends State> = { _(partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: boolean | undefined): void; }['_'];

When you want to define an action name like below:

set((state) => ({ ...state, whatever: 'any value' }), false, 'ADD_MEAL_KIT_ERROR');

I receive a typescript error Expected 1-2 arguments, but got 3.ts(2554)

I think we need to change the type definition of SetState to include action name
export declare type SetState<T extends State> = { _(partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: boolean | undefined, actionName?: string): void; }['_'];

@dai-shi
Copy link
Member

dai-shi commented Jun 1, 2022

Is this issue about devtools or just vanilla?

We have types for devtools here, overloading the one in vanilla.

setState(
...a: [...a: TakeTwo<A>, actionType?: string | { type: unknown }]
): Sr

vanilla doesn't support action names in JS, so we shouldn't add types for 3rd argument in TS.

@ZeroPie
Copy link

ZeroPie commented Jun 6, 2022

@dai-shi i'd say both
i am using slices and had the error too. Created a SetState with actionName to fix it:

import {GetState, State} from 'zustand'

type SetState<T extends State> = {
	_(partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: boolean | undefined): void, actionName?: string
}['_'];

export type StoreSlice<T extends object, E extends object = T> = (
set: SetState<E extends T ? E : E & T>,
get: GetState<E extends T ? E : E & T>
) => T;

export const useStore = create(devtools(createRootSlice));

@dai-shi
Copy link
Member

dai-shi commented Jun 6, 2022

That looks like a case with devtools.

Can anyone open a new issue with a reproduction with typescript playground?

It came to our attention that the SetStateType action name is not defined in zustand/src/vanilla.d.ts.

It's very intentional, so not an issue. Closing this.

@hasan-almujtaba
Copy link

Hello i following this Typescript Example for creating separated splices. It gave me error like Expected 1-2 arguments, but got 3 from SetState type. I'm using it in this repository.

@dai-shi
Copy link
Member

dai-shi commented Jun 21, 2022

https://github.com/hasan-almujtaba/next-starter/blob/10ade3d52a2a0eba949de03215b7e7545dcc498d/store/example.ts#L7
It's when you add more arguments in set?
It's because the types in L4 only accepts 1-2 arguments.

You need to type function by yourself, if inline function doesn't work for you.

const createExample = (set: (partial: (state: Store) => Partial<Store>, replace?: boolean, name?: string) => void, get: GetState<Store>) => ({
  count: 0,
  increment: () => {
    set((prev) => ({ count: prev.count + 1 }))
  },
})

Hope it works.

@WaleedZ90
Copy link
Author

It's a typescript issue, the type SetState doesn't have the actionName defined in it's type definition.
The work around that I've is extend the state myself.

type SetState<T extends State> = {
  _(partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: boolean | undefined, actionName?: string): void;
}['_'];

@dai-shi
Copy link
Member

dai-shi commented Jun 21, 2022

Copying my comment here from #1013 (comment) :
This is intentional because vanilla store doesn't know about the third parameter. Only devtools middleware has the idea and extends the type.


Let's invite @devanshj to explain the background better.

@devanshj
Copy link
Contributor

devanshj commented Jun 21, 2022

If one follows these intructions given in the docs for slices pattern one'd have no problems. In particular this instruction:

If you have some middlewares then replace StateCreator<MySlice, [], []> with StateCreator<MySlice, Mutators, []>. Eg if you're using devtools then it'll be StateCreator<MySlice, [["zustand/devtools", never]], []>.

Following those instructions would lead to the following code, which I think is the one that is being desired...

import create, { StateCreator } from 'zustand'
import { devtools } from "zustand/middleware"

interface BearSlice {
  bears: number
  addBear: () => void
}
const createBearSlice: StateCreator<BearSlice, [["zustand/devtools", never]], []> = (set) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 }), false, "addBear"),
})

const useStore = create<BearSlice>()(devtools((...a) => ({
  ...createBearSlice(...a)
})))

It's recommended to consult the docs before reporting an issue :)

Zustand has complex types internally so that there are no complex types externally on users end. If one follows the docs one'd find one has to annotate and type so little. See #710 if you want to learn about the complex internal types.

Hello i following this Typescript Example for creating separated splices.

I guess this sandbox is from the wiki? So I guess the issue here was that the old docs in the wiki were consulted instead of new ones. @dai-shi could you delete or redirect outdated wikis to new docs?

@hasan-almujtaba
Copy link

https://github.com/hasan-almujtaba/next-starter/blob/10ade3d52a2a0eba949de03215b7e7545dcc498d/store/example.ts#L7 It's when you add more arguments in set? It's because the types in L4 only accepts 1-2 arguments.

You need to type function by yourself, if inline function doesn't work for you.

const createExample = (set: (partial: (state: Store) => Partial<Store>, replace?: boolean, name?: string) => void, get: GetState<Store>) => ({
  count: 0,
  increment: () => {
    set((prev) => ({ count: prev.count + 1 }))
  },
})

Hope it works.

Thanks for your suggestion, it's work as expected.

@hasan-almujtaba
Copy link

If one follows these intructions given in the docs for slices pattern one'd have no problems. In particular this instruction:

If you have some middlewares then replace StateCreator<MySlice, [], []> with StateCreator<MySlice, Mutators, []>. Eg if you're using devtools then it'll be StateCreator<MySlice, [["zustand/devtools", never]], []>.

Following those instructions would lead to the following code, which I think is the one that is being desired...

import create, { StateCreator } from 'zustand'
import { devtools } from "zustand/middleware"

interface BearSlice {
  bears: number
  addBear: () => void
}
const createBearSlice: StateCreator<BearSlice, [["zustand/devtools", never]], []> = (set) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 }), false, "addBear"),
})

const useStore = create<BearSlice>()(devtools((...a) => ({
  ...createBearSlice(...a)
})))

It's recommended to consult the docs before reporting an issue :)

Zustand has complex types internally so that there are no complex types externally on users end. If one follows the docs one'd find one has to annotate and type so little. See #710 if you want to learn about the complex internal types.

Hello i following this Typescript Example for creating separated splices.

I guess this sandbox is from the wiki? So I guess the issue here was that the old docs in the wiki were consulted instead of new ones. @dai-shi could you delete or redirect outdated wikis to new docs?

Thanks for your instruction :)

i'm following sandbox from this wiki

@devanshj
Copy link
Contributor

i'm following sandbox from this wiki

Yeah I guessed that, no problems, it's our bad that we forgot to redirect the outdated wiki to new docs.

@hasan-almujtaba
Copy link

Just one question left, how to stack other middleware like persist?

Now i have following code

// store/index.ts

import create from 'zustand'
import { devtools, persist } from 'zustand/middleware'
import { Store } from '../types/store'
import createExample from './example'

const useStore = create<Store>()(
  devtools(
    persist((...a) => ({
      ...createExample(...a), // error here
    }))
  )
)

export default useStore
// store/example.ts

import { StateCreator } from 'zustand'
import { Store } from '../types/store'

const createExample: StateCreator<
  Store,
  [['zustand/devtools', never], ['zustand/persist', Store]],
  []
> = (set) => ({
  count: 0,
  increment: () => {
    set((prev) => ({ count: prev.count + 1 }))
  },
})

export default createExample
// types/store.ts
export interface Example {
  count: number
  increment: () => void
}

export type Store = Example

And this give me following error in index.ts :

Argument of type '[["zustand/devtools", never], ["zustand/persist", unknown]]' is not assignable to parameter of type '[["zustand/devtools", never], ["zustand/persist", Example]]'.
Type at position 1 in source is not compatible with type at position 1 in target.
Type 'unknown' is not assignable to type 'Example'.ts(2345)

@devanshj
Copy link
Contributor

Yeah that's a bit of a limitation, instead of writing ["zustand/persist", Example] write ["zustand/persist", unknown], like so: https://tsplay.dev/w1PnAW.

It won't make much of a difference, as you can see useStore still has the correct type ie StorePersist<BearSlice, Partial<BearSlice>> it's only the store received as parameter has StorePersist<BearSlice, unknown>.

The second type parameter is for the partialised state, so unless you're using redefining options with store.persist.setOptions({ partialize: ... }) it doesn't affect you.

@hasan-almujtaba
Copy link

Got it, thanks!

@dai-shi
Copy link
Member

dai-shi commented Jun 22, 2022

could you delete or redirect outdated wikis to new docs?

Done.

@hasan-almujtaba
Copy link

hasan-almujtaba commented Jul 10, 2022

Yeah that's a bit of a limitation, instead of writing ["zustand/persist", Example] write ["zustand/persist", unknown], like so: https://tsplay.dev/w1PnAW.

It won't make much of a difference, as you can see useStore still has the correct type ie StorePersist<BearSlice, Partial<BearSlice>> it's only the store received as parameter has StorePersist<BearSlice, unknown>.

The second type parameter is for the partialised state, so unless you're using redefining options with store.persist.setOptions({ partialize: ... }) it doesn't affect you.

Hello, how to do indepent slice with persist and devtools middleware like this? here my example, this example give me error https://tsplay.dev/wXQAON

@devanshj
Copy link
Contributor

That too is a (recently) known issue, see #1046 (comment) for a temporary fix. It probably will be fixed in next rc.

@hasan-almujtaba
Copy link

That too is a (recently) known issue, see #1046 (comment) for a temporary fix. It probably will be fixed in next rc.

I see, currently i try using interdependent slices and it's works as expected so for now i'm going to use this instead.

@Mugilan-Codes
Copy link

I am trying this as well. How to set only one slice to persist when using the slices pattern though?

@dbritto-dev
Copy link
Collaborator

I am trying this as well. How to set only one slice to persist when using the slices pattern though?

@Mugilan-Codes you can use partialize

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants