Created
April 18, 2024 04:51
-
-
Save asherccohen/c9eda21ea68f270c96968b6c34823ce4 to your computer and use it in GitHub Desktop.
Zustand context not finished
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createContext, PropsWithChildren, useContext, useRef } from "react"; | |
import { createStore, Mutate, useStore } from "zustand"; | |
type TState = Record<string, unknown>; | |
type TActions<State extends TState, P = unknown> = Record< | |
string, | |
(state: State, payload?: P) => Mutate<State, any> | |
>; | |
export function createStoreContext< | |
Name extends string, | |
Props extends TState, | |
State extends Props & { actions?: TActions<State> }, | |
>(displayName: Name, initialState: Partial<State> = {}) { | |
const createCustomStore = (initProps?: Partial<Props>) => { | |
return createStore<State>()((set) => ({ | |
...(initProps as State), | |
actions: initialState?.actions | |
? Object.entries(initialState.actions).reduce( | |
( | |
acc: Record< | |
string, | |
<S extends State, P = unknown>( | |
partial: | |
| S | |
| Partial<S> | |
| ((state: S, payload?: P) => S | Partial<S>) | |
) => void | |
>, | |
[actionKey, action] | |
) => { | |
// eslint-disable-next-line no-param-reassign | |
acc[actionKey] = (payload) => { | |
return set((state) => action(state, payload)); | |
}; | |
return acc; | |
}, | |
{} | |
) | |
: {}, | |
})); | |
}; | |
type Store = ReturnType<typeof createCustomStore>; | |
const CustomContext = createContext<Store | null>(null); | |
CustomContext.displayName = displayName; | |
type ProviderProps = PropsWithChildren<Props>; | |
function Provider({ children, ...props }: ProviderProps) { | |
const storeRef = useRef<Store>(); | |
if (!storeRef.current) { | |
storeRef.current = createCustomStore(initialState || props); | |
} | |
return ( | |
<CustomContext.Provider value={storeRef.current}> | |
{children} | |
</CustomContext.Provider> | |
); | |
} | |
const useCustomContext = <TSelected,>( | |
selector: (state: State) => TSelected, | |
equalityFn?: (left: TSelected, right: TSelected) => boolean | |
) => { | |
const store = useContext(CustomContext); | |
if (!store) { | |
throw new Error( | |
`use${displayName} must be called inside a ${displayName}Provider` | |
); | |
} | |
return [useStore(store, selector, equalityFn), store.setState] as const; | |
}; | |
const useCustomStore = () => { | |
const [state, setState] = useCustomContext((state) => state); | |
return [state, setState] as const; | |
}; | |
const useCustomStoreSelector = <TSelected,>( | |
selector: (state: State) => TSelected, | |
equalityFn?: (left: TSelected, right: TSelected) => boolean | |
) => { | |
const [state] = useCustomContext(selector, equalityFn); | |
return state; | |
}; | |
const useCustomStoreActions = () => { | |
const [actions, setState] = useCustomContext((state) => state.actions); | |
return actions; | |
}; | |
return [ | |
Provider, | |
useCustomStore, | |
useCustomStoreSelector, | |
useCustomStoreActions, | |
] as const; | |
} | |
export default createStoreContext; | |
// usage | |
type StoreState = { | |
grumpiness: number; | |
}; | |
const [ | |
Provider, | |
useCustomStore, | |
useCustomStoreSelector, | |
useCustomStoreActions, | |
] = createStoreContext("Positions", { | |
grumpiness: 1, | |
actions: { | |
//@ts-expect-error booo | |
increase: (state: StoreState, payload: { by: number }) => | |
state.grumpiness + payload.by, | |
}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment