Skip to content

Commit e3b0d55

Browse files
authored
Merge pull request #77 from microsoft/users/alebet/RR7
Fix DynamicModuleLoader and remove support for React-Redux 5
2 parents afed236 + 994c5ae commit e3b0d55

File tree

2 files changed

+88
-103
lines changed

2 files changed

+88
-103
lines changed

packages/redux-dynamic-modules-react/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
},
4545
"peerDependencies": {
4646
"react": ">= 15.0.0",
47-
"react-redux": ">= 5.0.0",
47+
"react-redux": ">= 6.0.0",
4848
"redux": ">= 3.0.0"
4949
},
5050
"dependencies": {

packages/redux-dynamic-modules-react/src/DynamicModuleLoader.tsx

+87-102
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import * as PropTypes from "prop-types";
21
import * as React from "react";
3-
//@ts-ignore
2+
//@ts-ignore // ReactReduxContext is not officially exported
43
import { Provider, ReactReduxContext } from "react-redux";
54

65
import {
@@ -13,16 +12,18 @@ export interface IDynamicModuleLoaderProps {
1312
/** Modules that need to be dynamically registerd */
1413
modules: IModuleTuple;
1514

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+
*/
1621
strictMode?: boolean;
1722

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. */
1924
createStore?: () => IModuleStore<any>;
2025
}
2126

22-
export interface IDynamicModuleLoaderContext {
23-
store: IModuleStore<any>;
24-
}
25-
2627
/**
2728
* The DynamicModuleLoader adds a way to register a module on mount
2829
* 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 {
3132
export class DynamicModuleLoader extends React.Component<
3233
IDynamicModuleLoaderProps
3334
> {
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+
);
7846
}
7947
}
8048

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> };
9052
}
9153

9254
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+
*/
9359
readyToRender: boolean;
9460
}
9561

9662
class DynamicModuleLoaderImpl extends React.Component<
9763
IDynamicModuleLoaderImplProps,
9864
IDynamicModuleLoaderImplState
9965
> {
66+
/** The modules that were added from this loader */
10067
private _addedModules?: IDynamicallyAddedModule;
68+
/** Flag that indicates we need to create a store/provider because a parent store was not provided */
10169
private _providerInitializationNeeded: boolean = false;
70+
/** The module store, derived from context */
10271
private _store: IModuleStore<any>;
103-
private _getLatestState: boolean;
72+
/** The react redux context, saved */
73+
private _memoizedReactReduxContext: any;
10474

10575
constructor(props: IDynamicModuleLoaderImplProps) {
10676
super(props);
10777

108-
this._store = this.props.store;
78+
this._store = props.reactReduxContext
79+
? props.reactReduxContext.store
80+
: undefined;
10981

11082
// We are not in strict mode, let's add the modules ASAP
11183
if (!this.props.strictMode) {
@@ -118,60 +90,73 @@ class DynamicModuleLoaderImpl extends React.Component<
11890
}
11991
}
12092

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>
130102
);
131103
}
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();
136106
}
137107

138-
this._addedModules = this._store.addModules(modules);
108+
return null;
139109
}
140110

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+
144133
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}
148139
</ReactReduxContext.Provider>
149140
);
150-
};
151-
152-
private _renderChildren = () => {
153-
if (this.props.children && typeof this.props.children === "function") {
154-
return this.props.children();
155-
}
141+
}
156142

157-
return this.props.children;
158-
};
143+
private _addModules(): void {
144+
const { createStore, modules } = this.props;
159145

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"
167154
);
168-
} else if (!this._getLatestState) {
169-
return this._renderChildren();
170155
}
171-
172-
return this._renderWithReactReduxContext();
156+
} else {
157+
// Add the modules here
158+
this._addedModules = this._store.addModules(modules);
173159
}
174-
return null;
175160
}
176161

177162
public componentDidMount() {

0 commit comments

Comments
 (0)