Skip to content

Commit 8dac91d

Browse files
authored
Fix react warning about causing a render during a render (#22)
1 parent 441332e commit 8dac91d

30 files changed

+19104
-116
lines changed

.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ typings/
5454
# Yarn Integrity file
5555
.yarn-integrity
5656

57-
# dotenv environment variables file
58-
.env
59-
6057
# next.js build output
6158
.next
6259

CONTRIBUTING.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Contributing
22

3+
## Local Development
4+
You can use the examples folder for local development. To do so, run the following:
5+
```bash
6+
npm install
7+
npm link
8+
cd example
9+
npm install
10+
npm link redux-injectors
11+
rm -rf node_modules/react
12+
rm -rf node_modules/react-dom
13+
rm -rf node_modules/react-redux
14+
15+
npm start
16+
```
17+
318
## Pull requests
419

520
Good pull requests - patches, improvements, new features - are a fantastic
@@ -26,11 +41,11 @@ included in the project:
2641

2742
```bash
2843
# Clone your fork of the repo into the current directory
29-
git clone https://github.com/<your-username>/react-boilerplate.git
44+
git clone https://github.com/<your-username>/redux-injectors.git
3045
# Navigate to the newly cloned directory
31-
cd react-boilerplate
46+
cd redux-injectors
3247
# Assign the original repo to a remote called "upstream"
33-
git remote add upstream https://github.com/react-boilerplate/react-boilerplate.git
48+
git remote add upstream https://github.com/react-boilerplate/redux-injectors.git
3449
```
3550

3651
2. If you cloned a while ago, get the latest changes from upstream:

README.md

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,33 @@ function createReducer(injectedReducers = {}) {
4343
const runSaga = sagaMiddleware.run;
4444
```
4545

46+
### Redux DevTools
47+
If you're using redux devtools, it's **important to set `shouldHotReload` to false**. This is because otherwise, redux devtools will re-dispatch previous actions when reducers are injected, causing unexpected behavior.
48+
49+
If using redux-toolkit:
50+
```js
51+
const store = configureStore({
52+
devTools: {
53+
shouldHotReload: false
54+
}
55+
})
56+
```
57+
58+
If not using redux-toolkit:
59+
```js
60+
import { composeWithDevTools } from 'redux-devtools-extension';
61+
62+
const composeEnhancers = composeWithDevTools({
63+
shouldHotReload: false
64+
});
65+
66+
const store = createStore(reducer, composeEnhancers(
67+
...
68+
));
69+
```
70+
71+
Unfortunately this causes a separate issue where the action history is cleared when a reducer is injected, **but it's still strongly recommended to set `shouldHotReload` to false**. There's an [open issue in the redux-devtools repo about this](https://github.com/reduxjs/redux-devtools/issues/378).
72+
4673
### Injecting your first reducer and saga
4774
After setting up the store, you will be able to start injecting reducers and sagas.
4875
```js
@@ -74,8 +101,31 @@ export default function BooksManager() {
74101
}
75102
```
76103

104+
**Note:** while the above usage should work in most cases, you might find your reducers/sagas aren't being injected in time to receive an action. This can happen, for example, if you dispatch an action inside a `useLayoutEffect` instead of a `useEffect`. In that case, `useInjectReducer` and `useInjectSaga` return boolean flags that are `true` once the reducers/sagas have finished injecting. You can check these before rendering children that depend on these reducers/sagas being injected.
105+
106+
```js
107+
import { useInjectReducer, useInjectSaga } from "redux-injectors";
108+
109+
export default function BooksManager(props) {
110+
const reducerInjected = useInjectReducer({ key: "books", reducer: booksReducer });
111+
const sagaInjected = useInjectSaga({ key: "books", saga: booksSaga });
112+
113+
if (!reducerInjected || !sagaInjected) {
114+
return null;
115+
}
116+
117+
return (
118+
<>
119+
{props.children}
120+
</>
121+
);
122+
}
123+
```
124+
125+
77126
## Documentation
78-
See the [**API reference**](docs/api.md)
127+
See the [**API reference**](docs/api.md)
128+
Or the [**example**](example)
79129

80130
## Motivation
81131
There's a few reasons why you might not want to load all your reducers and sagas upfront:

docs/api.md

Lines changed: 101 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,29 @@
66
- [createInjectorsEnhancer][2]
77
- [Parameters][3]
88
- [Examples][4]
9-
- [Injectors][5]
10-
- [injectReducer][6]
9+
- [Managers][5]
10+
- [createManager][6]
1111
- [Parameters][7]
1212
- [Examples][8]
13-
- [useInjectReducer][9]
14-
- [Parameters][10]
15-
- [Examples][11]
16-
- [injectSaga][12]
17-
- [Parameters][13]
18-
- [Examples][14]
19-
- [useInjectSaga][15]
20-
- [Parameters][16]
21-
- [Examples][17]
22-
- [Misc][18]
23-
- [forceReducerReload][19]
13+
- [Injectors][9]
14+
- [injectReducer][10]
15+
- [Parameters][11]
16+
- [Examples][12]
17+
- [useInjectReducer][13]
18+
- [Parameters][14]
19+
- [Examples][15]
20+
- [injectSaga][16]
21+
- [Parameters][17]
22+
- [Examples][18]
23+
- [useInjectSaga][19]
2424
- [Parameters][20]
2525
- [Examples][21]
26-
- [SagaInjectionModes][22]
27-
- [Properties][23]
26+
- [Misc][22]
27+
- [forceReducerReload][23]
28+
- [Parameters][24]
29+
- [Examples][25]
30+
- [SagaInjectionModes][26]
31+
- [Properties][27]
2832

2933
## Setup
3034

@@ -38,9 +42,9 @@ injectors to work properly
3842

3943
#### Parameters
4044

41-
- `params` **[Object][24]**
42-
- `params.runSaga` **[function][25]** A function that runs a saga. Should usually be `sagaMiddleware.run`
43-
- `params.createReducer` **[function][25]** A function that should create and
45+
- `params` **[Object][28]**
46+
- `params.runSaga` **[function][29]** A function that runs a saga. Should usually be `sagaMiddleware.run`
47+
- `params.createReducer` **[function][29]** A function that should create and
4448
return the root reducer. It's passed the injected reducers as the first
4549
parameter. These should be added to the root reducer using `combineReducer`
4650
or a similar method.
@@ -71,6 +75,33 @@ const store = createStore(
7175
)
7276
```
7377

78+
## Managers
79+
80+
81+
82+
83+
### createManager
84+
85+
Creates a "manager" component that will inject the provided reducer and saga
86+
when mounted. It only renders its children after both the reducer and saga
87+
have been injected. This is the recommended way to use redux-injectors.
88+
89+
#### Parameters
90+
91+
- `options` **[Object][28]**
92+
- `options.name` **[function][29]** The name to give the manager that shows up in the react devtools
93+
- `options.key` **[string][30]** The key to inject the reducer under
94+
- `options.reducer` **[function][29]** The reducer that will be injected
95+
- `options.saga` **[function][29]** The saga that will be injected
96+
97+
#### Examples
98+
99+
```javascript
100+
const BooksManager = createManager({ name: "BooksManager", key: "books", reducer: booksReducer, saga: booksSaga })
101+
```
102+
103+
Returns **ComponentType&lt;{children: ReactNode}>** The manager
104+
74105
## Injectors
75106

76107

@@ -83,9 +114,9 @@ component is instantiated
83114

84115
#### Parameters
85116

86-
- `params` **[Object][24]**
87-
- `params.key` **[string][26]** The key to inject the reducer under
88-
- `params.reducer` **[function][25]** The reducer that will be injected
117+
- `params` **[Object][28]**
118+
- `params.key` **[string][30]** The key to inject the reducer under
119+
- `params.reducer` **[function][29]** The reducer that will be injected
89120

90121
#### Examples
91122

@@ -105,9 +136,9 @@ A react hook that dynamically injects a reducer when the hook is run
105136

106137
#### Parameters
107138

108-
- `params` **[Object][24]**
109-
- `params.key` **[string][26]** The key to inject the reducer under
110-
- `params.reducer` **[function][25]** The reducer that will be injected
139+
- `params` **[Object][28]**
140+
- `params.key` **[string][30]** The key to inject the reducer under
141+
- `params.reducer` **[function][29]** The reducer that will be injected
111142

112143
#### Examples
113144

@@ -119,6 +150,8 @@ function BooksManager() {
119150
}
120151
```
121152

153+
Returns **[boolean][31]** flag indicating whether or not the reducer has finished injecting
154+
122155
### injectSaga
123156

124157
A higher-order component that dynamically injects a saga when the component
@@ -127,13 +160,13 @@ dictate how and when the saga should be injected and ejected
127160

128161
#### Parameters
129162

130-
- `params` **[Object][24]**
131-
- `params.key` **[string][26]** The key to inject the saga under
132-
- `params.saga` **[function][25]** The saga that will be injected
133-
- `params.mode` **[string][26]?** The injection behaviour to use. The default is
163+
- `params` **[Object][28]**
164+
- `params.key` **[string][30]** The key to inject the saga under
165+
- `params.saga` **[function][29]** The saga that will be injected
166+
- `params.mode` **[string][30]?** The injection behaviour to use. The default is
134167
`SagaInjectionModes.DAEMON` which causes the saga to be started on component
135168
instantiation and never canceled or started again. @see
136-
[SagaInjectionModes][22] for the other possible modes.
169+
[SagaInjectionModes][26] for the other possible modes.
137170

138171
#### Examples
139172

@@ -153,13 +186,9 @@ A react hook that dynamically injects a saga when the hook is run
153186

154187
#### Parameters
155188

156-
- `params` **[Object][24]**
157-
- `params.key` **[string][26]** The key to inject the saga under
158-
- `params.saga` **[function][25]** The saga that will be injected
159-
- `params.mode` **[string][26]?** The injection behaviour to use. The default is
160-
`SagaInjectionModes.DAEMON` which causes the saga to be started on component
161-
instantiation and never canceled or started again. @see
162-
[SagaInjectionModes][22] for the other possible modes.
189+
- `params` **[Object][28]**
190+
- `params.key` **[string][30]** The key to inject the saga under
191+
- `params.saga` **[function][29]** The saga that will be injected
163192

164193
#### Examples
165194

@@ -171,6 +200,8 @@ function BooksManager() {
171200
}
172201
```
173202

203+
Returns **[boolean][31]** flag indicating whether or not the saga has finished injecting
204+
174205
## Misc
175206

176207

@@ -198,15 +229,14 @@ An enum of all the possible saga injection behaviours
198229

199230
#### Properties
200231

201-
- `RESTART_ON_REMOUNT` **[String][26]** The saga will be started on component instantiation and cancelled with
232+
- `RESTART_ON_REMOUNT` **[String][30]** The saga will be started on component instantiation and cancelled with
202233
`task.cancel()` on component unmount for improved performance.
203-
- `DAEMON` **[String][26]** Causes the saga to be started on component instantiation and never canceled
234+
- `DAEMON` **[String][30]** Causes the saga to be started on component instantiation and never canceled
204235
or started again.
205-
- `ONCE_TILL_UNMOUNT` **[String][26]** Behaves like 'RESTART_ON_REMOUNT' but never runs it again.
206-
- `COUNTER` **[String][26]** The saga will be mounted similar to 'RESTART_ON_REMOUNT', only difference is that
207-
saga will be mounted only once on first inject, and ejected when all injectors are unmounted.
208-
So this enables you to have multiple injectors with same saga and key, only one instance of saga will run
209-
and enables you to have system that are more similar to widgets
236+
- `ONCE_TILL_UNMOUNT` **[String][30]** Behaves like 'RESTART_ON_REMOUNT' but never runs it again.
237+
- `COUNTER` **[String][30]** Similar to 'RESTART_ON_REMOUNT' except the
238+
saga will be mounted only once on first inject and ejected when all injectors are unmounted.
239+
This enables you to have multiple injectors with the same saga and key and only one instance of the saga will run.
210240

211241
[1]: #setup
212242

@@ -216,46 +246,56 @@ An enum of all the possible saga injection behaviours
216246

217247
[4]: #examples
218248

219-
[5]: #injectors
249+
[5]: #managers
220250

221-
[6]: #injectreducer
251+
[6]: #createmanager
222252

223253
[7]: #parameters-1
224254

225255
[8]: #examples-1
226256

227-
[9]: #useinjectreducer
257+
[9]: #injectors
228258

229-
[10]: #parameters-2
259+
[10]: #injectreducer
230260

231-
[11]: #examples-2
261+
[11]: #parameters-2
232262

233-
[12]: #injectsaga
263+
[12]: #examples-2
234264

235-
[13]: #parameters-3
265+
[13]: #useinjectreducer
236266

237-
[14]: #examples-3
267+
[14]: #parameters-3
238268

239-
[15]: #useinjectsaga
269+
[15]: #examples-3
240270

241-
[16]: #parameters-4
271+
[16]: #injectsaga
242272

243-
[17]: #examples-4
273+
[17]: #parameters-4
244274

245-
[18]: #misc
275+
[18]: #examples-4
246276

247-
[19]: #forcereducerreload
277+
[19]: #useinjectsaga
248278

249279
[20]: #parameters-5
250280

251281
[21]: #examples-5
252282

253-
[22]: #sagainjectionmodes
283+
[22]: #misc
284+
285+
[23]: #forcereducerreload
286+
287+
[24]: #parameters-6
288+
289+
[25]: #examples-6
290+
291+
[26]: #sagainjectionmodes
292+
293+
[27]: #properties
254294

255-
[23]: #properties
295+
[28]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
256296

257-
[24]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
297+
[29]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
258298

259-
[25]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
299+
[30]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
260300

261-
[26]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
301+
[31]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean

documentation.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
toc:
22
- name: Setup
33
children:
4-
- createInjectorsEnhancer
4+
- createInjectorsEnhancer
5+
- name: Managers
6+
children:
7+
- createManager
58
- name: Injectors
69
children:
710
- injectReducer

example/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SKIP_PREFLIGHT_CHECK=true

0 commit comments

Comments
 (0)