-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathzustand-constate.tsx
113 lines (96 loc) · 2.77 KB
/
zustand-constate.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import React, {
createContext,
useContext,
useRef,
type ReactNode,
useLayoutEffect,
} from 'react'
import {
createStore as createZustandStore,
type StateCreator,
type StoreApi,
} from 'zustand'
import { useStoreWithEqualityFn } from 'zustand/traditional'
import { useShallow } from 'zustand/react/shallow'
export type LocalUseStore<TState, Props> = TState & {
$sync: (props: Props) => void
}
type CreateContextUseStore<StateSlice, TState extends unknown> = (
selector?: (s: TState) => StateSlice | undefined,
equalityFn?: (a: StateSlice, b: StateSlice) => boolean
) => StateSlice
const syncSelector = <TState, Props>(store: LocalUseStore<TState, Props>) =>
store.$sync
export function createZustandConstate<
TState extends unknown,
Props extends Record<string, any>,
>(
createState?: StateCreator<TState>,
useValue?: (
props: Props & {
useStore: CreateContextUseStore<any, TState>
useStoreApi: StoreApi<LocalUseStore<TState, Props>>
}
) => any
) {
const StoreContext = createContext<StoreApi<
LocalUseStore<TState, Props>
> | null>(null)
const useStoreInContext = (selector: any) => {
const store = useContext(StoreContext)
if (!store) {
throw new Error('Missing StoreProvider')
}
// @ts-ignore
return useStoreWithEqualityFn(store, useShallow(selector))
}
const Hook = (props: Props) => {
const sync = useStoreInContext(syncSelector)
const storeApi = useContext(StoreContext)
useLayoutEffect(() => {
// @ts-ignore
sync(props)
}, Object.values(props))
if (useValue) {
// @ts-ignore
const returned = useValue({
...props,
useStore: useStoreInContext,
useStoreApi: storeApi,
})
useLayoutEffect(() => {
// @ts-ignore
if (returned && typeof returned === 'object') sync(returned)
}, [returned])
}
return null
}
createState = createState || (() => ({}) as TState)
const StoreProvider = (props: Props & { children: ReactNode }) => {
const { children, ...propsWithoutChildren } = props
const storeRef = useRef<StoreApi<LocalUseStore<TState, Props>>>()
if (!storeRef.current) {
storeRef.current = createZustandStore<LocalUseStore<TState, Props>>(
(set, get, api) => ({
// @ts-ignore
...createState?.(set, get, api),
$sync: (props: Partial<Props>) =>
set((state) => ({ ...state, ...props })),
})
)
}
return (
// @ts-ignore
<StoreContext.Provider value={storeRef.current}>
{/* @ts-ignore*/}
<Hook {...propsWithoutChildren} />
{children}
</StoreContext.Provider>
)
}
return {
Provider: StoreProvider,
useStore: useStoreInContext,
}
}
export default createZustandConstate