-
Notifications
You must be signed in to change notification settings - Fork 4
/
redux-hooks.tsx
119 lines (98 loc) · 2.97 KB
/
redux-hooks.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
114
115
116
117
118
119
import {Store} from "redux";
import ReactDOM from "react-dom";
import React, {useContext, useState, useEffect, useRef} from "react";
import {shallowEqual} from "./shallow-equal";
interface ContextType {
store?: Store;
updaters: Function[];
}
const StoreContext = React.createContext<ContextType>({
updaters: [],
});
export function HooksProvider(props: {
store: Store;
children: React.ReactNode;
}) {
// Mutable updaters list of all useReduxState() users
const updaters: Function[] = [];
useEffect(() => {
// Setup only one listener for the provider
return props.store.subscribe(() => {
// so we can batch update all hook users
ReactDOM.unstable_batchedUpdates(() => {
for (const update of updaters) {
update();
}
});
});
}, [props.store]);
// Context values never update. We put the store directly and the updates
// list into it
return (
<StoreContext.Provider value={{store: props.store, updaters: updaters}}>
{props.children}
</StoreContext.Provider>
);
}
interface MapState<T> {
(state: any): T;
}
class NoProviderError extends Error {
constructor() {
super("<HooksProvider> wrapping missing for useRedux*()?");
}
}
/**
* Use Redux dispatch
*/
export function useReduxDispatch() {
const {store} = useContext(StoreContext);
if (!store) {
throw new NoProviderError();
}
return store.dispatch;
}
/**
* Use part of the redux state
*/
export function useReduxState<T = any>(mapState?: MapState<T>): T {
const {store, updaters} = useContext(StoreContext);
if (!store) {
throw new NoProviderError();
}
/**
* Get mapped value from the state
*/
const getMappedValue = () => {
const state = store.getState();
if (mapState) {
return mapState(state);
}
return state;
};
// Use ref to avoid useless state mapping
const initialSliceContainer = useRef<T | null>(null);
if (!initialSliceContainer.current) {
initialSliceContainer.current = getMappedValue();
}
const [stateSlice, setState] = useState(initialSliceContainer.current!);
useEffect(() => {
let prev: T | null = initialSliceContainer.current;
const update = () => {
const next = getMappedValue();
if (!shallowEqual(prev, next)) {
setState(next);
prev = next;
}
};
// Mutate the updaters list so the subscription in provider can update
// this hook
updaters.push(update);
return () => {
// Remove the updater on unmount
const index = updaters.indexOf(update);
updaters.splice(index, 1);
};
}, [store]);
return stateSlice;
}