Skip to content

Fix react warning about causing a render during a render #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ typings/
# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# next.js build output
.next

Expand Down
21 changes: 18 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Contributing

## Local Development
You can use the examples folder for local development. To do so, run the following:
```bash
npm install
npm link
cd example
npm install
npm link redux-injectors
rm -rf node_modules/react
rm -rf node_modules/react-dom
rm -rf node_modules/react-redux

npm start
```

## Pull requests

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

```bash
# Clone your fork of the repo into the current directory
git clone https://github.com/<your-username>/react-boilerplate.git
git clone https://github.com/<your-username>/redux-injectors.git
# Navigate to the newly cloned directory
cd react-boilerplate
cd redux-injectors
# Assign the original repo to a remote called "upstream"
git remote add upstream https://github.com/react-boilerplate/react-boilerplate.git
git remote add upstream https://github.com/react-boilerplate/redux-injectors.git
```

2. If you cloned a while ago, get the latest changes from upstream:
Expand Down
52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,33 @@ function createReducer(injectedReducers = {}) {
const runSaga = sagaMiddleware.run;
```

### Redux DevTools
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.

If using redux-toolkit:
```js
const store = configureStore({
devTools: {
shouldHotReload: false
}
})
```

If not using redux-toolkit:
```js
import { composeWithDevTools } from 'redux-devtools-extension';

const composeEnhancers = composeWithDevTools({
shouldHotReload: false
});

const store = createStore(reducer, composeEnhancers(
...
));
```

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).

### Injecting your first reducer and saga
After setting up the store, you will be able to start injecting reducers and sagas.
```js
Expand Down Expand Up @@ -74,8 +101,31 @@ export default function BooksManager() {
}
```

**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.

```js
import { useInjectReducer, useInjectSaga } from "redux-injectors";

export default function BooksManager(props) {
const reducerInjected = useInjectReducer({ key: "books", reducer: booksReducer });
const sagaInjected = useInjectSaga({ key: "books", saga: booksSaga });

if (!reducerInjected || !sagaInjected) {
return null;
}

return (
<>
{props.children}
</>
);
}
```


## Documentation
See the [**API reference**](docs/api.md)
See the [**API reference**](docs/api.md)
Or the [**example**](example)

## Motivation
There's a few reasons why you might not want to load all your reducers and sagas upfront:
Expand Down
162 changes: 101 additions & 61 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,29 @@
- [createInjectorsEnhancer][2]
- [Parameters][3]
- [Examples][4]
- [Injectors][5]
- [injectReducer][6]
- [Managers][5]
- [createManager][6]
- [Parameters][7]
- [Examples][8]
- [useInjectReducer][9]
- [Parameters][10]
- [Examples][11]
- [injectSaga][12]
- [Parameters][13]
- [Examples][14]
- [useInjectSaga][15]
- [Parameters][16]
- [Examples][17]
- [Misc][18]
- [forceReducerReload][19]
- [Injectors][9]
- [injectReducer][10]
- [Parameters][11]
- [Examples][12]
- [useInjectReducer][13]
- [Parameters][14]
- [Examples][15]
- [injectSaga][16]
- [Parameters][17]
- [Examples][18]
- [useInjectSaga][19]
- [Parameters][20]
- [Examples][21]
- [SagaInjectionModes][22]
- [Properties][23]
- [Misc][22]
- [forceReducerReload][23]
- [Parameters][24]
- [Examples][25]
- [SagaInjectionModes][26]
- [Properties][27]

## Setup

Expand All @@ -38,9 +42,9 @@ injectors to work properly

#### Parameters

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

## Managers




### createManager

Creates a "manager" component that will inject the provided reducer and saga
when mounted. It only renders its children after both the reducer and saga
have been injected. This is the recommended way to use redux-injectors.

#### Parameters

- `options` **[Object][28]**
- `options.name` **[function][29]** The name to give the manager that shows up in the react devtools
- `options.key` **[string][30]** The key to inject the reducer under
- `options.reducer` **[function][29]** The reducer that will be injected
- `options.saga` **[function][29]** The saga that will be injected

#### Examples

```javascript
const BooksManager = createManager({ name: "BooksManager", key: "books", reducer: booksReducer, saga: booksSaga })
```

Returns **ComponentType&lt;{children: ReactNode}>** The manager

## Injectors


Expand All @@ -83,9 +114,9 @@ component is instantiated

#### Parameters

- `params` **[Object][24]**
- `params.key` **[string][26]** The key to inject the reducer under
- `params.reducer` **[function][25]** The reducer that will be injected
- `params` **[Object][28]**
- `params.key` **[string][30]** The key to inject the reducer under
- `params.reducer` **[function][29]** The reducer that will be injected

#### Examples

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

#### Parameters

- `params` **[Object][24]**
- `params.key` **[string][26]** The key to inject the reducer under
- `params.reducer` **[function][25]** The reducer that will be injected
- `params` **[Object][28]**
- `params.key` **[string][30]** The key to inject the reducer under
- `params.reducer` **[function][29]** The reducer that will be injected

#### Examples

Expand All @@ -119,6 +150,8 @@ function BooksManager() {
}
```

Returns **[boolean][31]** flag indicating whether or not the reducer has finished injecting

### injectSaga

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

#### Parameters

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

#### Examples

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

#### Parameters

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

#### Examples

Expand All @@ -171,6 +200,8 @@ function BooksManager() {
}
```

Returns **[boolean][31]** flag indicating whether or not the saga has finished injecting

## Misc


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

#### Properties

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

[1]: #setup

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

[4]: #examples

[5]: #injectors
[5]: #managers

[6]: #injectreducer
[6]: #createmanager

[7]: #parameters-1

[8]: #examples-1

[9]: #useinjectreducer
[9]: #injectors

[10]: #parameters-2
[10]: #injectreducer

[11]: #examples-2
[11]: #parameters-2

[12]: #injectsaga
[12]: #examples-2

[13]: #parameters-3
[13]: #useinjectreducer

[14]: #examples-3
[14]: #parameters-3

[15]: #useinjectsaga
[15]: #examples-3

[16]: #parameters-4
[16]: #injectsaga

[17]: #examples-4
[17]: #parameters-4

[18]: #misc
[18]: #examples-4

[19]: #forcereducerreload
[19]: #useinjectsaga

[20]: #parameters-5

[21]: #examples-5

[22]: #sagainjectionmodes
[22]: #misc

[23]: #forcereducerreload

[24]: #parameters-6

[25]: #examples-6

[26]: #sagainjectionmodes

[27]: #properties

[23]: #properties
[28]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

[24]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[29]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function

[25]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[30]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

[26]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[31]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
5 changes: 4 additions & 1 deletion documentation.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
toc:
- name: Setup
children:
- createInjectorsEnhancer
- createInjectorsEnhancer
- name: Managers
children:
- createManager
- name: Injectors
children:
- injectReducer
Expand Down
1 change: 1 addition & 0 deletions example/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SKIP_PREFLIGHT_CHECK=true
Loading