Skip to content

Commit 5e73f81

Browse files
committed
feat: fundamental zustand integration
1 parent 7692042 commit 5e73f81

File tree

10 files changed

+196
-11
lines changed

10 files changed

+196
-11
lines changed

.babelrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
{ "@utils": "./src/utils/" },
2020
{ "@assets": "./src/assets/" },
2121
{ "@event-emitter": "./src/services/event-emitter" },
22-
{ "@local-storage": "./src/services/local-storage" }
22+
{ "@local-storage": "./src/services/local-storage" },
23+
{ "@zustand": "./src/services/zustand/store" }
2324
],
2425
"extensions": [".js", ".jsx", ".ts", ".tsx"]
2526
}

index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module "*.png";
2+
declare module "*.jpg";
3+
declare module "*.jpeg";
4+
declare module "*.json";

package-lock.json

Lines changed: 53 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
"react-native-dynamic-vector-icons": "^1.2.0",
2020
"react-native-gesture-handler": "^2.5.0",
2121
"react-native-localization": "^2.3.1",
22+
"react-native-mmkv-storage": "^0.8.0",
2223
"react-native-reanimated": "^2.9.1",
2324
"react-native-safe-area-context": "^4.3.1",
2425
"react-native-screens": "^3.15.0",
2526
"react-native-splash-screen": "^3.3.0",
2627
"react-native-vector-icons": "^9.2.0",
27-
"react-navigation-helpers": "^2.1.0"
28+
"react-navigation-helpers": "^2.1.0",
29+
"zustand": "^4.0.0"
2830
},
2931
"devDependencies": {
3032
"@babel/core": "^7.18.10",

src/services/models/index.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
export interface ICardItem {
1+
export interface IUser {
22
name: string;
3-
description: string;
4-
language: string;
5-
star: number;
6-
fork: number;
3+
email: string;
74
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { StoreSlice } from "@zustand";
2+
3+
export interface AppSlice {
4+
isWalkthroughAvailable: boolean;
5+
setWalkthrough: (value: boolean) => void;
6+
isDarkMode: boolean;
7+
setDarkMode: (value: boolean) => void;
8+
}
9+
10+
const createAppSlice: StoreSlice<AppSlice> = (set) => ({
11+
isWalkthroughAvailable: true,
12+
setWalkthrough: (value: boolean) => set({ isWalkthroughAvailable: value }),
13+
isDarkMode: true,
14+
setDarkMode: (value: boolean) => set({ isDarkMode: value }),
15+
});
16+
17+
export default createAppSlice;

src/services/zustand/context.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {
2+
createContext as reactCreateContext,
3+
createElement,
4+
useContext,
5+
useRef,
6+
type ReactNode,
7+
} from "react";
8+
import type { UseBoundStore, StoreApi } from "zustand";
9+
10+
type UseContextStore<T extends object> = {
11+
(): T;
12+
<U>(selector: (s: T) => U, equalityFn?: (a: T, b: T) => boolean): U;
13+
};
14+
15+
function createContext<
16+
TState extends object,
17+
TUseBoundStore extends UseBoundStore<StoreApi<object>> = UseBoundStore<
18+
StoreApi<object>
19+
>,
20+
>() {
21+
const ZustandContext = reactCreateContext<TUseBoundStore | undefined>(
22+
undefined,
23+
);
24+
25+
const Provider = ({
26+
createStore,
27+
children,
28+
}: {
29+
createStore: () => TUseBoundStore;
30+
children: ReactNode;
31+
}) => {
32+
const storeRef = useRef<TUseBoundStore>();
33+
34+
if (!storeRef.current) {
35+
storeRef.current = createStore();
36+
}
37+
38+
return createElement(
39+
ZustandContext.Provider,
40+
{ value: storeRef.current },
41+
children,
42+
);
43+
};
44+
45+
const useStore: UseContextStore<TState> = <StateSlice>(
46+
selector?: (s: TState) => StateSlice,
47+
equalityFn = Object.is,
48+
) => {
49+
// ZustandContext value is guaranteed to be stable.
50+
const useProviderStore = useContext(ZustandContext);
51+
if (!useProviderStore) {
52+
throw new Error(
53+
"Seems like you have not used zustand provider as an ancestor.",
54+
);
55+
}
56+
57+
return useProviderStore(selector as (s: object) => StateSlice, equalityFn);
58+
};
59+
60+
return {
61+
Provider,
62+
ZustandContext,
63+
useStore,
64+
};
65+
}
66+
67+
export default createContext;

src/services/zustand/store.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import create, { StoreApi } from "zustand";
2+
import { persist, StateStorage } from "zustand/middleware";
3+
import { MMKVLoader } from "react-native-mmkv-storage";
4+
import createAppSlice, { AppSlice } from "@services/zustand/app/AppSlice";
5+
import createUserSlice, { UserSlice } from "@services/zustand/user/UserSlice";
6+
7+
const sessionStorage = new MMKVLoader()
8+
.withInstanceID("sessionStorage")
9+
.initialize();
10+
11+
export type StoreState = AppSlice & UserSlice;
12+
export type StoreSlice<T> = (
13+
set: StoreApi<StoreState>["setState"],
14+
get: StoreApi<StoreState>["getState"],
15+
) => T;
16+
17+
const useStore = create<StoreState>()(
18+
persist(
19+
(set, get) => ({
20+
...createAppSlice(set, get),
21+
...createUserSlice(set, get),
22+
}),
23+
{
24+
name: "store",
25+
getStorage: () => sessionStorage as StateStorage,
26+
},
27+
),
28+
);
29+
30+
export default useStore;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { StoreSlice } from "@zustand";
2+
import { IUser } from "@services/models";
3+
4+
export interface UserSlice {
5+
userData: IUser | null;
6+
setUserData: (user: IUser) => void;
7+
resetUserData: () => void;
8+
}
9+
10+
const createUserSlice: StoreSlice<UserSlice> = (set) => ({
11+
userData: null,
12+
setUserData: (user: IUser) => set({ userData: user }),
13+
resetUserData: () => set({ userData: null }),
14+
});
15+
16+
export default createUserSlice;

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"@screens/*": ["./screens/*"],
4040
"@utils": ["./utils/"],
4141
"@assets": ["./assets/"],
42-
"@local-storage": ["./services/local-storage"]
42+
"@local-storage": ["./services/local-storage"],
43+
"@zustand": ["./services/zustand/store"]
4344
}
4445
},
4546
"exclude": [

0 commit comments

Comments
 (0)