Skip to content

Instantly share code, notes, and snippets.

@asherccohen
Created April 18, 2024 04:51
Show Gist options
  • Save asherccohen/c9eda21ea68f270c96968b6c34823ce4 to your computer and use it in GitHub Desktop.
Save asherccohen/c9eda21ea68f270c96968b6c34823ce4 to your computer and use it in GitHub Desktop.
Zustand context not finished
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