Git Product home page Git Product logo

dhmk-zustand-lens's People

Contributors

dhmk083 avatar hideoo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

dhmk-zustand-lens's Issues

[Question] How do I initialize the store with props?

In this example, initProps contains data/state only, no actions. It is also not a lens object (this is where my problem happens I think).

I think what I need to do is initialize the props as a lens object and merge it with the default state but I don't know how to do that. Any help would be appreciated

const useStore = create(withLenses(() => ({
  ...initProps,

  bears: lens(set => ({
    // ...bears state & actions
  })),

  fish: lens(set => ({
    // ...fish state & actions
  })),

  // Placing the initial props here will set the actions to undefined
  // ...initProps
})))

Typescript example has typescript error `Type ... is missing the following properties from StoreApi`

Hey.

Codesandbox

I tried following typescript example but ran into error. I then tried to recreate the code from example but it does show an error, too.

I provided codesandbox where example typescript code is in file fromexample.ts and my store is in file mystore.ts

After providing the store missing properties getState, setState, subscribe and destroy (which I shouldn't do) it now shows an error

Type 'StoreApi<Store>' is missing the following properties from type 'Store': id, name, nestedts

but these properties are definitely defined.

I don't understand how to type the store properly. Can you please provide direction?

setState and Typescript, unable to update slice partially.

I use useStore.setState to mock actions within my tests. I'm running into an issue where Typescript complains about partially updating a slice.

ie.

interface SearchSlice {
  text: string;
  updateText: () => void,
  otherFunc: () => void
}

interface Store {
    search: SearchSlice;
}

const useStore = create<Store>(withLens(() => ({ search: lens(searchSlice }))
)
//-----my test file

let mockedFun = jest.fn();

        beforeEach(() => {
            useStore.setState({
                search: {
                    updateText: mockedFun
                },
            });

simple example but i would get a typescript error saying

Type '{ updateText: Mock<any, any>; }' is missing the following properties from type 'SearchSlice': otherFunc, text

Any ideas on how to work fix this or workaround this? (excluding always supplying properties or ignoring the error)

Thanks in advance

Just wanted to say thanks for this project

Sorry for opening an issue for this when really there's no issue to be reported, but just wanted to say thanks for building this and sharing it here, just what I needed for my project and it works wonderfully.

Setting field of type boolean to true/false raises type error

This

export const test = create<{ loading: boolean }>(withLenses({ loading: true }))

raises the following error:

Argument of type 'StateCreator<{ loading: true; }, [], []>' is not assignable to parameter of type 'StateCreator<{ loading: boolean; }, [], []>'.
    Types of parameters 'getState' and 'getState' are incompatible.
      Call signature return types '{ loading: boolean; }' and '{ loading: true; }' are incompatible.
        The types of 'loading' are incompatible between these types.
          Type 'boolean' is not assignable to type 'true'.

Type conflict with StateCreator

Since SetState has been deprecated in zustand version 4.0.0, it is better to use StateCreator instead, but there is a type conflict:

Argument of type 'readonly string[]' is not assignable to parameter of type '[]'.
  The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type '[]'.

Code:

interface IStore {
  readonly app: IStoreApp;
}

type IStoreApp = ILanguageState;

const useStore = create<IStore>(
  withLenses({
    app: lens<IStoreApp, IStoreApp>((...accessors) => ({
      ...languageState(...accessors), // Error in `...accessors`
      ...initState?.app,
    })),
  })
);
export interface ILanguageState {
  readonly language: string;
  readonly setLanguage: (language: string) => void;
}

export const languageState: StateCreator<ILanguageState, [], [], ILanguageState> = (set) => ({
  language: "en",
  setLanguage: (language) =>
    set(() => ({
      language,
    })),
});

And I do not know if this is bug or not, but in the generic type of the lens function, you have to pass the state type 2 times, otherwise there will be the following error:

Argument of type 'StoreApi<object>' is not assignable to parameter of type 'StoreApi<ILanguageState>'.
  The types returned by 'getState()' are incompatible between these types.
    Type 'object' is not assignable to type 'ILanguageState'.

Source code:

export declare type Lens<T extends State, S extends State | StoreApi<State> = State, Setter extends SetState2<T> = SetState2<T>> = (set: Setter, get: GetState<T>, api: ResolveStoreApi<S>, path: ReadonlyArray<string>) => T;

And the problem seems to be that generic type of the api argument is passed S, instead of T, and since (according to the documentation) we pass only 1 type, the api will always be StoreApi<object>, which causes conflicts.

Possible solution:

- export declare type Lens<T extends State, S extends State | StoreApi<State> = State, Setter extends SetState2<T> = SetState2<T>> = (set: Setter, get: GetState<T>, api: ResolveStoreApi<S>, path: ReadonlyArray<string>) => T;
+ export declare type Lens<T extends State, S extends State | StoreApi<State> = State, Setter extends SetState2<T> = SetState2<T>> = (set: Setter, get: GetState<T>, api: ResolveStoreApi<T>, path: ReadonlyArray<string>) => T;

Using with `devtools` middleware

Hi, I love your project as it allows me to do exactly what I need without too much type boilerplate. However, I have one question when using your library together with the devtools middleware.

devtools middleware extends the setter function, so you can supply the name of the action as a third argument to set. But I can't seem to make that work together with this library. Do you have any ideas on how to do that? Thank you!

Example code:

interface UsersSlice {
  byName: Map<string, User>
  listUsers: () => void
}

const usersSlice: Lens<UsersSlice> = (set) => ({
  byName: new Map(),

  async listUsers() {
    const data = await listUsersFromServer()
    const byName = new Map(data.users.map((user) => [user.name, user]))

    set({ byName }, false, '@users/listUsers')
  },
})

export const useStore = create(
  devtools(
    immer(
      withLenses(() => ({
        users: lens(usersSlice),
      }))
    ),
    { serialize: true }
  )
)

Error when using global store with Typescript

I am trying to have a global store that can be retrieve inside lens for my store.

"@dhmk/zustand-lens": "^4.0.0",
"zustand": "^4.5.2"

Here's my code.

interface Store {
  auth: AuthSlice;
  sidebar: SidebarSlice;
  loading: boolean;
}

export const useStore = create<Store>()(
  devtools(
    withLenses(() => ({
      auth: lens<AuthSlice>(createAuthSlice),
      sidebar: lens<SidebarSlice>(createSidebarSlice),
      loading: false,
    })),
  )
);

So that in my slices, I can access the store by:

(set, get, store) => ({
  // ...
  store.setState({ loading: false });
})

There's no error popping up in my IDE, but get this error when starting react.

ERROR in src/stores/store.ts:15:16
TS2345: Argument of type '() => { auth: LensOpaqueType<AuthSlice, unknown>; sidebar: LensOpaqueType<SidebarSlice, unknown>; loading: boolean; }' is not assignable to parameter of type 'CheckLenses<{ auth: AuthSlice & LensTypeInfo<AuthSlice, StoreApi<unknown>>; sidebar: SidebarSlice & LensTypeInfo<SidebarSlice, StoreApi<...>>; loading: boolean; } & LensMeta<...>, WithDevtools<...>> | StateCreator<...>'.
  Type '() => { auth: LensOpaqueType<AuthSlice, unknown>; sidebar: LensOpaqueType<SidebarSlice, unknown>; loading: boolean; }' is not assignable to type 'StateCreator<{ auth: AuthSlice & LensTypeInfo<AuthSlice, StoreApi<unknown>>; sidebar: SidebarSlice & LensTypeInfo<SidebarSlice, StoreApi<...>>; loading: boolean; }, [...], [], CheckLenses<...>>'.
    Type '() => { auth: LensOpaqueType<AuthSlice, unknown>; sidebar: LensOpaqueType<SidebarSlice, unknown>; loading: boolean; }' is not assignable to type '(setState: <A extends string | { type: string; }>(partial: { auth: AuthSlice & LensTypeInfo<AuthSlice, StoreApi<unknown>>; sidebar: SidebarSlice & LensTypeInfo<...>; loading: boolean; } | Partial<...> | ((state: { ...; }) => { ...; } | Partial<...>), replace?: boolean, action?: A) => void, getState: () => { ...; }, ...'.
      Type '{ auth: LensOpaqueType<AuthSlice, unknown>; sidebar: LensOpaqueType<SidebarSlice, unknown>; loading: boolean; }' is not assignable to type 'CheckLenses<{ auth: AuthSlice & LensTypeInfo<AuthSlice, StoreApi<unknown>>; sidebar: SidebarSlice & LensTypeInfo<SidebarSlice, StoreApi<...>>; loading: boolean; } & LensMeta<...>, WithDevtools<...>>'.
        Property 'loading' is incompatible with index signature.
          Type 'boolean' has no properties in common with type 'LensTypeInfo<unknown, StoreApi<unknown>>'.
    13 | export const useStore = create<Store>()(
    14 |   devtools(
  > 15 |     withLenses(() => ({
       |                ^^^^^^^^
  > 16 |       auth: lens<AuthSlice>(createAuthSlice),
       | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  > 17 |       sidebar: lens<SidebarSlice>(createSidebarSlice),
       | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  > 18 |       loading: false,
       | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  > 19 |     })),
       | ^^^^^^^
    20 |   )
    21 | );
    22 |

Ts issue with lens method

import { create, StateCreator } from 'zustand'
import { devtools } from 'zustand/middleware'
import { withLenses, lens } from '@dhmk/zustand-lens'

export interface PayrollType {
  balance: number
  setBalance: (value: number) => void
}

export const createPayrollSlice: StateCreator<PayrollType> = (set) => ({
  balance: 0,
  setBalance: (value): void => set(() => ({ balance: value })),
})

interface FinanceStore {
  payroll: PayrollType
}

export const useFinanceStore = create<FinanceStore>()(
  devtools(
    withLenses(() => ({
      payroll: lens<PayrollType>((set, get, api) => createPayrollSlice(set, get, api)),
    })),
    {
      name: 'FinanceStore',
      enabled: process.env.NODE_ENV === 'development',
    },
  ),
)

Error
type 'PayrollType' is not assignable to type 'CheckLenses<PayrollType, WithDevtools<StoreApi>> & LensMeta<CheckLenses<PayrollType, WithDevtools<StoreApi<...>>>, unknown>'.
Type 'PayrollType' is not assignable to type 'LensMeta<CheckLenses<PayrollType, WithDevtools<StoreApi>>, unknown>'.
Index signature for type 'string' is missing in type 'PayrollType'.ts(2322)

I tried manuall typing it and spent sometime it still failed

Setup store to be readonly

I'm loving this library. We're already using it, so it's going into production.

However, I noticed that due to an error, we used a sort function (which mutates arrays), causing the application to crash because Immer prohibits mutating the store.

We replaced the usage of .sort with .toSorted, resolving the issue since .toSorted is a non-mutating method. Nevertheless, we'd like TypeScript to assist us with this in the future.

How can we ensure that the store's type is readonly? I attempted to make all lenses ReadonlyDeep (using the type-fest utility), but that seems to result in a cascade of TypeScript issues.

Doesnt seem to work with persist middleware

Everything works fine when not using the persist middleware, however when the persist middleware is used the action function within the nested sub store becomes undefined and doesnt work anymore after page refresh

I have created a sandbox(with nextjs) that recreates the issue that I am talking about https://codesandbox.io/s/zustand-lens-persist-k0pxs5?file=/pages/index.tsx

Steps to reproduce the issue

  1. Open sandbox and on the first load, everything works as intended. clicking on the "change text" button simply appends "s" to the "text"
  2. Now refresh the codesandbox browser and you will notice that clicking on the button doesnt do anything(on my local I get the error "TypeError: changeText is not a function")

Any help is appreciated,
Thanks in advance

Expose the global state in the lenses

It would be nice if it were possible to access global state in the lenses. I.e., change the lens signature from:

lens(fn: (set, get) => T): T

to

lens(fn: (set, get, getRoot) => T): T

or something like that.

possibility to call another slice action from inside a lens

From a slice X,Y,Z I need to call an action of a slice COMMON that has a shared method among all other slices.

I used to call it this way:

get().COMMON.setToast({ toast: UPDATE_ROWS_CATEGORY_SUCCESS })

once I wrap the slice with the lens() method get() only returns the type of the lensed slice i'm working on. Is it possibile to let get() access the whole store?

Unable to resolve peer dependency 'zustand@"^4.0.0"'

Context

Working on a React web app initialized with Vite, Trying out Zustand state management library. Needed a way to structure app state in a nested fashion

// for example
State
├ Authentication: AuthenticationSTate
├ UserSettings: UserSettingsState
├ RecentVisits: RecentVisitsState
├ etc...

After research this library seemed to be the solution for such an implementation, however when I tried installing it I kept getting the following error

npm WARN Could not resolve dependency:
npm WARN peer zustand@"^4.0.0" from @dhmk/[email protected]

I am currently using [email protected] tried looking for a non-rc 4.0.0. version but with no luck.

After going through the code on zustand-4 branch "package.json" it looks like the version given for zustand doesn't exist.

How can zukeeper be added as middleware?

when add zukeeper as middleware like so:

import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
import zukeeper from 'zukeeper'
import { withLenses } from '@dhmk/zustand-lens'
import { categories } from '../slices/categories'
import { projects } from '../slices/projects'
import { ui } from '../slices/ui'
import { IStore } from './types'
import { brandStyles } from '../slices/brandStyles'
import { listItemsInstance } from '../slices/listItemsInstance'

const useApplicationStore = create<IStore>()(
  zukeeper(
    immer(
      withLenses(() => ({
        ui,
        categories,
        brandStyles,
        projects,
        listItemsInstance,
      })),
    ),
  ),
)

window.store = useApplicationStore

export { useApplicationStore }

I get this error:

main.3b53a5f….js:54795 Uncaught TypeError: Cannot set properties of undefined (setting 'setState')
    at eval (immer.mjs:9:18)
    at eval (index.js:6:19)
    at createStoreImpl (vanilla.mjs:32:32)
    at createStore (vanilla.mjs:35:52)
    at createImpl (index.mjs:43:111)
    at eval (index.ts:12:1)
    at ./src/zustandStore/store/index.ts (main.3b53a5f….js:52692:1)
    at __webpack_require__ (main.3b53a5f….js:54792:33)
    at fn (main.3b53a5f….js:55045:21)
    at eval (LoginModal.tsx:23:78)

TypeScript/Immer Support

Would you be able to provide an example of using this library with TypeScript and Immer please?

Using getters in state

In case anyone needs this, it is possible to add ES6 getters support.

Example:

const store = create(
      withLenses(() => ({
        sub: lens2((set, get) => ({
          items: [1, 2],

          get count() {
            return get().items.length;
          },

          add(x) {
            set({ items: get().items.concat(x) });
          },
        })),
      }))
    );

There are caveats, however:

  • It may degrade performance. Here's a benchmark. It is especially daunting in Firefox.
  • It won't work with Immer.
  • It won't work with nested lenses (can be solved).

See https://github.com/dhmk083/dhmk-zustand-lens/tree/getters for more.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.