-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
142 lines (114 loc) · 3.55 KB
/
index.ts
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import * as React from 'react'
type AdapterFactory = <Options extends {}, ToInject extends {}>(
hook: (_?: Options) => ToInject,
compare?: <P extends {}>(props: P, nextProps: P) => boolean
) => <P>(_: React.ComponentType<P>) => React.ComponentType<
& Parameters<typeof compare>[0]
& Options
& Partial<ToInject>
& Pick<P, Exclude<keyof P, keyof ToInject>>
>
export const createAdapter: AdapterFactory = (hook, propsAreEqual) => component => {
const memoised = React.memo(component, propsAreEqual)
return props => React.createElement(memoised, {
...hook(props), props,
...props
} as any)
}
export const identity = <T>(_: T) => _
type Mutations<T> = {
[K in keyof T]: T[K] extends (..._) => any
? (..._: Parameters<T[K]>) => void
: () => void
}
const createMutations = <T, M extends Updates<T>>(
updates: M,
dispatch: (_: Update<T>) => void
) => {
const mutations: Partial<Mutations<M>> = {}
for (const action in updates) {
const update = updates[action]
mutations[action] = ((...args) => dispatch(
typeof update === 'function'
? (update as any)(...args)
: update
)) as any
}
return mutations as Mutations<M>
}
export type ProviderModule<T, P = {}> = {
(): T
Provider: React.ForwardRefExoticComponent<
& React.PropsWithoutRef<ProviderProps<P>>
& React.RefAttributes<T>
>
}
type ProviderProps<P> = P & { children: React.ReactNode }
const useUnboundRef = <T>(value, ref?: React.Ref<T>) => {
let functionRef = ref as (_: T | null) => void
if (typeof ref !== 'function') {
const mutableRef = ref as React.MutableRefObject<T>
functionRef = (value: T = null) => mutableRef.current = value
}
React.useEffect(() => {
if (!ref) return
functionRef(value)
return () => functionRef(null)
}, [])
}
export const createReader = <
State,
UpdateFactories extends Updates<State>,
Props extends {} = {},
>(
factory: (_?: Props) => State,
updates: UpdateFactories = {} as any
) => function useStore (initial: Props): State & Mutations<UpdateFactories> {
const make = () => {
const model = factory(initial)
return Object.assign(model, createMutations(
updates, update => dispatch(update) // dispatch to be lazily dereferenced
))
}
const [ value, dispatch ] = React.useReducer(
reducer,
React.useMemo(make, [])
)
return value
}
const createHook = <
State,
UpdateFactories extends Updates<State>,
Props extends {} = {}
>(
initial: (_?: Props) => State,
updates: UpdateFactories = {} as any
) => {
const context = React.createContext({})
const useBoundContext = () => React.useContext(context)
const useStore = createReader(initial, updates)
type Service = ReturnType<typeof useStore>
const Provider = React.forwardRef<Service, ProviderProps<Props>>((props, ref) => {
const { children, ...providerProps } = props
const value = useStore(providerProps as any)
useUnboundRef(value, ref)
return React.createElement(context.Provider, { children, value })
})
return Object.assign(useBoundContext, { Provider }) as ProviderModule<Service, Props>
}
export default createHook
export type Service<T extends ProviderModule<any>> = ReturnType<T>
// for ergonomic reason, T should not be of function type
export type Update<T> = { (_: T): void | Partial<T> }
type Updates<T> = Record<string,
| ((..._) => Update<T>)
| Partial<T>
>
const reducer = <T>(state: T, update: Update<T>): T => {
const nextState = typeof update === 'function'
? update(state)
: update
return nextState
? { ...state, ...nextState }
: state
}