1
- import * as PropTypes from "prop-types" ;
2
1
import * as React from "react" ;
3
- //@ts -ignore
2
+ //@ts -ignore // ReactReduxContext is not officially exported
4
3
import { Provider , ReactReduxContext } from "react-redux" ;
5
4
6
5
import {
@@ -13,16 +12,18 @@ export interface IDynamicModuleLoaderProps {
13
12
/** Modules that need to be dynamically registerd */
14
13
modules : IModuleTuple ;
15
14
15
+ /**
16
+ * Set this flag to indicate that this component is being rendered in 'Strict Mode'
17
+ * React 'StrictMode' does not allow constructor side-effects, so we defer adding modules to componentDidMount
18
+ * when this flag is set.
19
+ * This has the effect of adding a second render.
20
+ */
16
21
strictMode ?: boolean ;
17
22
18
- /** Optional callback which returns a store instance. This would be called if no store could be loaded from the context. */
23
+ /** Optional callback which returns a store instance. This would be called if no store could be loaded from th e context. */
19
24
createStore ?: ( ) => IModuleStore < any > ;
20
25
}
21
26
22
- export interface IDynamicModuleLoaderContext {
23
- store : IModuleStore < any > ;
24
- }
25
-
26
27
/**
27
28
* The DynamicModuleLoader adds a way to register a module on mount
28
29
* When this component is initialized, the reducer and saga from the module passed as props will be registered with the system
@@ -31,81 +32,52 @@ export interface IDynamicModuleLoaderContext {
31
32
export class DynamicModuleLoader extends React . Component <
32
33
IDynamicModuleLoaderProps
33
34
> {
34
- // @ts -ignore
35
- private static contextTypes = {
36
- store : PropTypes . object ,
37
- } ;
38
-
39
- constructor (
40
- props : IDynamicModuleLoaderProps ,
41
- context : IDynamicModuleLoaderContext
42
- ) {
43
- super ( props , context ) ;
44
- }
45
-
46
- /**
47
- * Render a Redux provider
48
- */
49
- public render ( ) : React . ReactNode {
50
- if ( ReactReduxContext ) {
51
- return (
52
- < ReactReduxContext . Consumer >
53
- { context => {
54
- return (
55
- < DynamicModuleLoaderImpl
56
- createStore = { this . props . createStore }
57
- store = { context ? context . store : undefined }
58
- strictMode = { this . props . strictMode }
59
- modules = { this . props . modules } >
60
- { this . props . children }
61
- </ DynamicModuleLoaderImpl >
62
- ) ;
63
- } }
64
- </ ReactReduxContext . Consumer >
65
- ) ;
66
- } else {
67
- return (
68
- < DynamicModuleLoaderImpl
69
- // @ts -ignore
70
- createStore = { this . props . createStore }
71
- store = { this . context . store }
72
- strictMode = { this . props . strictMode }
73
- modules = { this . props . modules } >
74
- { this . props . children }
75
- </ DynamicModuleLoaderImpl >
76
- ) ;
77
- }
35
+ public render ( ) {
36
+ return (
37
+ < ReactReduxContext . Consumer >
38
+ { reactReduxContext => (
39
+ < DynamicModuleLoaderImpl
40
+ { ...this . props }
41
+ reactReduxContext = { reactReduxContext }
42
+ />
43
+ ) }
44
+ </ ReactReduxContext . Consumer >
45
+ ) ;
78
46
}
79
47
}
80
48
81
- interface IDynamicModuleLoaderImplProps {
82
- /** Modules that need to be dynamically registerd */
83
- modules : IModuleTuple ;
84
-
85
- store : IModuleStore < any > ;
86
-
87
- strictMode : boolean ;
88
-
89
- createStore ?: ( ) => IModuleStore < any > ;
49
+ interface IDynamicModuleLoaderImplProps extends IDynamicModuleLoaderProps {
50
+ /** The react-redux context passed from the <Provider> component */
51
+ reactReduxContext ?: { store : IModuleStore < any > } ;
90
52
}
91
53
92
54
interface IDynamicModuleLoaderImplState {
55
+ /** Is the DML component ready to render.
56
+ * If strictMode is set to false, this will be set to true initially
57
+ * If strict mode is set to true, this will be set after the first render completes
58
+ */
93
59
readyToRender : boolean ;
94
60
}
95
61
96
62
class DynamicModuleLoaderImpl extends React . Component <
97
63
IDynamicModuleLoaderImplProps ,
98
64
IDynamicModuleLoaderImplState
99
65
> {
66
+ /** The modules that were added from this loader */
100
67
private _addedModules ?: IDynamicallyAddedModule ;
68
+ /** Flag that indicates we need to create a store/provider because a parent store was not provided */
101
69
private _providerInitializationNeeded : boolean = false ;
70
+ /** The module store, derived from context */
102
71
private _store : IModuleStore < any > ;
103
- private _getLatestState : boolean ;
72
+ /** The react redux context, saved */
73
+ private _memoizedReactReduxContext : any ;
104
74
105
75
constructor ( props : IDynamicModuleLoaderImplProps ) {
106
76
super ( props ) ;
107
77
108
- this . _store = this . props . store ;
78
+ this . _store = props . reactReduxContext
79
+ ? props . reactReduxContext . store
80
+ : undefined ;
109
81
110
82
// We are not in strict mode, let's add the modules ASAP
111
83
if ( ! this . props . strictMode ) {
@@ -118,60 +90,73 @@ class DynamicModuleLoaderImpl extends React.Component<
118
90
}
119
91
}
120
92
121
- private _addModules ( ) : void {
122
- const { createStore , modules } = this . props ;
123
- if ( ! this . _store ) {
124
- if ( createStore ) {
125
- this . _store = createStore ( ) ;
126
- this . _providerInitializationNeeded = true ;
127
- } else {
128
- throw new Error (
129
- "Store could not be resolved from React context"
93
+ public render ( ) : React . ReactNode {
94
+ if ( this . state . readyToRender ) {
95
+ if ( this . _providerInitializationNeeded ) {
96
+ return (
97
+ < Provider store = { this . _store } >
98
+ { /* We just rendered the provider, so now we need to render
99
+ DML again. This one will add the modules */ }
100
+ < DynamicModuleLoader { ... this . props } />
101
+ </ Provider >
130
102
) ;
131
103
}
132
- } else {
133
- // We will add modules dynamically and due to github issue https://github.com/Microsoft/redux-dynamic-modules/issues/27#issuecomment-464905893
134
- // 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
135
- this . _getLatestState = ReactReduxContext ;
104
+
105
+ return this . _renderLoader ( ) ;
136
106
}
137
107
138
- this . _addedModules = this . _store . addModules ( modules ) ;
108
+ return null ;
139
109
}
140
110
141
- private _renderWithReactReduxContext = ( ) => {
142
- const { store } = this . props ;
143
- // store.getState is important here as we don't want to use storeState from the provided context
111
+ /**
112
+ * Render a Redux provider
113
+ */
114
+ private _renderLoader ( ) : React . ReactNode {
115
+ if ( this . props . reactReduxContext == null ) {
116
+ const message =
117
+ "Tried to render DynamicModuleLoader, but no ReactReduxContext was provided" ;
118
+ console . error ( message ) ;
119
+
120
+ throw new Error ( message ) ;
121
+ }
122
+
123
+ // Memoize the context if it has changed upstream
124
+ // If the context has not changed, we want to use the same object reference so that
125
+ // downstream consumers do not update needlessly
126
+ if ( this . props . reactReduxContext !== this . _memoizedReactReduxContext ) {
127
+ this . _memoizedReactReduxContext = {
128
+ ...this . props . reactReduxContext ,
129
+ storeState : this . props . reactReduxContext . store . getState ( ) ,
130
+ } ;
131
+ }
132
+
144
133
return (
145
- < ReactReduxContext . Provider
146
- value = { { store, storeState : store . getState ( ) } } >
147
- { this . _renderChildren ( ) }
134
+ < ReactReduxContext . Provider value = { this . _memoizedReactReduxContext } >
135
+ { this . props . children &&
136
+ typeof this . props . children === "function"
137
+ ? this . props . children ( )
138
+ : this . props . children }
148
139
</ ReactReduxContext . Provider >
149
140
) ;
150
- } ;
151
-
152
- private _renderChildren = ( ) => {
153
- if ( this . props . children && typeof this . props . children === "function" ) {
154
- return this . props . children ( ) ;
155
- }
141
+ }
156
142
157
- return this . props . children ;
158
- } ;
143
+ private _addModules ( ) : void {
144
+ const { createStore , modules } = this . props ;
159
145
160
- public render ( ) : React . ReactNode {
161
- if ( this . state . readyToRender ) {
162
- if ( this . _providerInitializationNeeded ) {
163
- return (
164
- < Provider store = { this . _store } >
165
- { this . _renderChildren ( ) }
166
- </ Provider >
146
+ if ( ! this . _store ) {
147
+ // If we need to create a store, do that here. We will skip adding the modules and render DML again
148
+ if ( createStore ) {
149
+ this . _store = createStore ( ) ;
150
+ this . _providerInitializationNeeded = true ;
151
+ } else {
152
+ throw new Error (
153
+ "Store could not be resolved from React context"
167
154
) ;
168
- } else if ( ! this . _getLatestState ) {
169
- return this . _renderChildren ( ) ;
170
155
}
171
-
172
- return this . _renderWithReactReduxContext ( ) ;
156
+ } else {
157
+ // Add the modules here
158
+ this . _addedModules = this . _store . addModules ( modules ) ;
173
159
}
174
- return null ;
175
160
}
176
161
177
162
public componentDidMount ( ) {
0 commit comments