Skip to content

Instantly share code, notes, and snippets.

@kyle-mccarthy
Last active June 27, 2024 01:23
Show Gist options
  • Save kyle-mccarthy/cae2df1089c71b9d6f5eb55992a15474 to your computer and use it in GitHub Desktop.
Save kyle-mccarthy/cae2df1089c71b9d6f5eb55992a15474 to your computer and use it in GitHub Desktop.
namespaced slices in zustand
import type { GetState, PartialState, SetState, State } from "zustand";
const noop = (..._: unknown[]): void => {
/* noop */
};
export type Setter<T extends State> = (
s: Partial<T> | ((prev: T) => Partial<T>),
replace?: boolean
) => void;
export type Factory<T extends State> = (set: Setter<T>, get: () => T) => T;
// Zustand recommnds splitting the store into separate "slices", but doesn't
// really provide a great way to do this. [^slices]
//
// This allows for easily creating slices that are encapsulated as an object in
// the primary store. `createSlice` does this by re-scoping the get and set
// functions to the slice based on the property name passed as the first arg.
//
// # Example
//
// ## Create the slice for the primary store
// ```
// interface UserSlice {
// id?: number;
// email?: string;
// setId: (id: number) => void;
// setEmail: (email: string) => void;
// }
//
// const userFactory: Factory<AddressSlice> = (set, _get) => ({
// setId: (id: number) => set({ id }),
// setEmail: (email: string) => set({ email }),
// });
//
// const createUserSlice = createSlice("user", userFactory);
// ```
//
// ## Create the store and include your slice
// ```
// import { create } from "zustand";
//
// interface Store {
// user: UserSlice;
// ...
// }
//
// const store = create((set, get) => ({
// user: createUserSlice(set, get),
// }));
// ```
//
// [^slices]: https://github.com/pmndrs/zustand/wiki/Splitting-the-store-into-separate-slices
export const createSlice = <
P extends keyof Z,
S extends State,
Z extends { [k in P]: S }
>(
property: P,
factory: Factory<S>
): ((set: SetState<Z>, get: GetState<Z>) => S) => {
return (set, get) => {
const getter = (): S => {
return get()[property];
};
const setter: Setter<S> = (arg, replace) => {
const prev = get()[property];
const next = typeof arg === "function" ? arg(prev) : arg;
if (next === prev) {
return noop();
}
if (replace) {
return set({ [property]: next as S } as PartialState<Z>);
}
return set({
[property]: {
...prev,
...next,
} as S,
} as PartialState<Z>);
};
return factory(setter, getter);
};
};
@kyle-mccarthy
Copy link
Author

@kyle-mccarthy line 82:

const prev: Z[P]
Spread types may only be created from object types

Zustand used to treat all state as an object. It looks like that changed recently.

@Harag9
Copy link

Harag9 commented Jan 25, 2024

I've tried using this and being new to Zustand I can't get it to work fully, as the 4 imports are all deprecated.

import { GetState, PartialState, SetState, State } from "zustand";

When I use it the 2nd one of the 2 below works, however as soon as I wrap immer, devtools or even persist around it (the first one), I get red squiggle lines. Would it be possible for you to update the above code to use the latest version of Zustand?

export const useStoreFactory = createSelectors(
create(
immer((set, get) => ({
user: createUserSlice(set, get),
userSettings: createUserSettingsSlice(set, get),
}))
)
);

export const useStoreFactoryWorks = createSelectors(
create((set, get) => ({
user: createUserSlice(set, get),
userSettings: createUserSettingsSlice(set, get),
}))
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment