/
DynamicModuleLoader.tsx
193 lines (167 loc) · 6.1 KB
/
DynamicModuleLoader.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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import * as PropTypes from "prop-types";
import * as React from "react";
//@ts-ignore
import { Provider, ReactReduxContext } from "react-redux";
import {
IDynamicallyAddedModule,
IModuleStore,
IModuleTuple,
} from "redux-dynamic-modules-core";
export interface IDynamicModuleLoaderProps {
/** Modules that need to be dynamically registerd */
modules: IModuleTuple;
strictMode?: boolean;
/** Optional callback which returns a store instance. This would be called if no store could be loaded from the context. */
createStore?: () => IModuleStore<any>;
}
export interface IDynamicModuleLoaderContext {
store: IModuleStore<any>;
}
/**
* The DynamicModuleLoader adds a way to register a module on mount
* When this component is initialized, the reducer and saga from the module passed as props will be registered with the system
* On unmount, they will be unregistered
*/
export class DynamicModuleLoader extends React.Component<
IDynamicModuleLoaderProps
> {
// @ts-ignore
private static contextTypes = {
store: PropTypes.object,
};
constructor(
props: IDynamicModuleLoaderProps,
context: IDynamicModuleLoaderContext
) {
super(props, context);
}
/**
* Render a Redux provider
*/
public render(): React.ReactNode {
if (ReactReduxContext) {
return (
<ReactReduxContext.Consumer>
{context => {
return (
<DynamicModuleLoaderImpl
createStore={this.props.createStore}
store={context ? context.store : undefined}
strictMode={this.props.strictMode}
modules={this.props.modules}>
{this.props.children}
</DynamicModuleLoaderImpl>
);
}}
</ReactReduxContext.Consumer>
);
} else {
return (
<DynamicModuleLoaderImpl
// @ts-ignore
createStore={this.props.createStore}
store={this.context.store}
strictMode={this.props.strictMode}
modules={this.props.modules}>
{this.props.children}
</DynamicModuleLoaderImpl>
);
}
}
}
interface IDynamicModuleLoaderImplProps {
/** Modules that need to be dynamically registerd */
modules: IModuleTuple;
store: IModuleStore<any>;
strictMode: boolean;
createStore?: () => IModuleStore<any>;
}
interface IDynamicModuleLoaderImplState {
readyToRender: boolean;
}
class DynamicModuleLoaderImpl extends React.Component<
IDynamicModuleLoaderImplProps,
IDynamicModuleLoaderImplState
> {
private _addedModules?: IDynamicallyAddedModule;
private _providerInitializationNeeded: boolean = false;
private _store: IModuleStore<any>;
private _getLatestState: boolean;
constructor(props: IDynamicModuleLoaderImplProps) {
super(props);
this._store = this.props.store;
// We are not in strict mode, let's add the modules ASAP
if (!this.props.strictMode) {
this._addModules();
this.state = { readyToRender: true };
} else {
// We are in strict mode, so have to wait for CDM to add modules.
// Thus, we cannot render the children at this time
this.state = { readyToRender: false };
}
}
private _addModules(): void {
const { createStore, modules } = this.props;
if (!this._store) {
if (createStore) {
this._store = createStore();
this._providerInitializationNeeded = true;
} else {
throw new Error(
"Store could not be resolved from React context"
);
}
} else {
// We will add modules dynamically and due to github issue https://github.com/Microsoft/redux-dynamic-modules/issues/27#issuecomment-464905893
// The very first render will not get latest state, to fix that we will need to get latest state from store directly on first render
this._getLatestState = ReactReduxContext;
}
this._addedModules = this._store.addModules(modules);
}
private _renderWithReactReduxContext = () => {
const { store } = this.props;
// store.getState is important here as we don't want to use storeState from the provided context
return (
<ReactReduxContext.Provider
value={{ store, storeState: store.getState() }}>
{this._renderChildren()}
</ReactReduxContext.Provider>
);
};
private _renderChildren = () => {
if (this.props.children && typeof this.props.children === "function") {
return this.props.children();
}
return this.props.children;
};
public render(): React.ReactNode {
if (this.state.readyToRender) {
if (this._providerInitializationNeeded) {
return (
<Provider store={this._store}>
{this._renderChildren()}
</Provider>
);
} else if (!this._getLatestState) {
return this._renderChildren();
}
return this._renderWithReactReduxContext();
}
return null;
}
public componentDidMount() {
if (this.props.strictMode) {
this._addModules();
this.setState({ readyToRender: true });
}
}
/**
* Unregister sagas and reducers
*/
public componentWillUnmount(): void {
if (this._addedModules) {
this._addedModules.remove();
this._addedModules = undefined;
}
}
}