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

Not working with Context - createStore #12

Closed
godzzo opened this issue Mar 29, 2024 · 2 comments · Fixed by #13
Closed

Not working with Context - createStore #12

godzzo opened this issue Mar 29, 2024 · 2 comments · Fixed by #13

Comments

@godzzo
Copy link

godzzo commented Mar 29, 2024

Hello!

I tried to use with createStore, If I see it correctly it is only for BoundStore.
So I created a little modification for it. It is not nice because I have to disable the rules-of-hooks.
(I should decouple the ContextProvider too, and review typing...)

Stackblitz Sample

What do you think?

export function createSelectorFunctions<StateType extends object>(
	store: StoreApi<StateType>,
) {
	const storeIn = store as any;

	const keys = Object.keys(storeIn.getState());
	storeIn.use = {};

	keys.forEach((key) => {
		const selector = (state: StateType) => state[key as keyof StateType];

		// eslint-disable-next-line react-hooks/rules-of-hooks
		storeIn.use[key] = () => useStore(storeIn, selector as any);
	});

	return store as StoreApi<StateType> & ZustandFuncSelectors<StateType>;
}

store.ts sample:

import { createStore } from 'zustand';
import { combine } from 'zustand/middleware';

import { createProvider } from './util';

function initCountStore() {
  return createStore(
    combine({ count: 0, label: 'hello' }, (set) => ({
      inc: () => set((state) => ({ count: state.count + 1 })),
      dec: () => set((state) => ({ count: state.count - 1 })),
      setLabel: (label: string) => set({ label }),
    }))
  );
}

export const { Provider: CountProvider, useSelectors: useCount } =
  createProvider(initCountStore);

util.tsx file:

import { ReactNode, createContext, useContext, useRef } from 'react';

import { StoreApi, useStore } from 'zustand';

type ExtractWrite<T> = T extends StoreApi<infer U> ? U : never;
type ContextValue<T> = T & ZustandFuncSelectors<ExtractWrite<T>>;

export function createProvider<T extends StoreApi<object>>(initStore: () => T) {
  const ValueContext = createContext<ContextValue<T>>(null as any);

  const Provider = ({ children }: { children: ReactNode }) => {
    const storeRef = useRef<ContextValue<T>>();

    if (!storeRef.current) {
      storeRef.current = createSelectorFunctions(initStore()) as any;
    }

    return (
      <ValueContext.Provider value={storeRef.current!!}>
        {children}
      </ValueContext.Provider>
    );
  };

  const useSelectors = () => useContext(ValueContext);

  return {
    Provider,
    useSelectors,
  };
}

/* --- * UTILITY - from `auto-zustand-selectors-hook` * --- */

export interface ZustandFuncSelectors<StateType> {
  use: {
    [key in keyof StateType]: () => StateType[key];
  };
}

export function createSelectorFunctions<StateType extends object>(
  store: StoreApi<StateType>
) {
  const storeIn = store as any;

  const keys = Object.keys(storeIn.getState());
  storeIn.use = {};

  keys.forEach((key) => {
    const selector = (state: StateType) => state[key as keyof StateType];

    // eslint-disable-next-line react-hooks/rules-of-hooks
    storeIn.use[key] = () => useStore(storeIn, selector as any);
  });

  return store as StoreApi<StateType> & ZustandFuncSelectors<StateType>;
}
@Kamilcat
Copy link
Contributor

@godzzo

I've made following workaround with improved typing, also it doesn't trigger ESLint's React hooks rule:

import { type StoreApi, useStore } from 'zustand'

type FuncSelectors<T> = {
  [key in keyof T]: () => T[key]
}

export interface ZustandFuncSelectors<StateType> {
  use: FuncSelectors<StateType>
}

export function createSelectorFunctions<StateType extends object>(
  store: StoreApi<StateType>
) {
  const storeIn: StoreApi<StateType> & ZustandFuncSelectors<StateType> = {
    ...store,
    use: Object.fromEntries(
      Object.entries(store.getState()).map(([key]) => [
        key as keyof StateType,
        () => useStore(storeIn, (state: StateType) => state[key as keyof StateType])
      ])
    ) as FuncSelectors<StateType>
  }

  return storeIn
}

@Albert-Gao
Copy link
Owner

Albert-Gao commented May 19, 2024

Hi, guys, really sorry for the long wait, thanks for the info provided here,

In this commit: 58e00e1

  • fixed the error when use with Context
  • fixed the type error when use with Context
  • added tests against the latest version of Zustand

PR #13 here, will be merged soon

Albert-Gao added a commit that referenced this issue May 19, 2024
chore: 🌱 upgraded all pkgs to the latest, all works (Thanks godzzo and Kamilcat)
fix: 📈 now the Context usage works, also fixed the types
close Not working with Context - createStore #12
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

Successfully merging a pull request may close this issue.

3 participants