Skip to content

Commit 7ab076c

Browse files
committed
feat: support hooks update on HMR, fixes #1256
1 parent dbf1047 commit 7ab076c

File tree

5 files changed

+52
-1
lines changed

5 files changed

+52
-1
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,22 @@ export default hot(App);
5353

5454
4. If you need hooks support, use React-🔥-Dom
5555

56+
### Hook support
57+
58+
To enable hot hook update you have to enable it (not setting by default for now):
59+
60+
```js
61+
import { setConfig } from 'react-hot-loader';
62+
63+
setConfig({
64+
hotHooks: true,
65+
});
66+
```
67+
68+
With this option set **all** `useEffects`, `useCallbacks` and `useMemo` would be updated on Hot Module Replacement.
69+
70+
Please try it so we can enable it by default. (or not)
71+
5672
## React-🔥-Dom
5773

5874
React-🔥-Dom ([hot-loader/react-dom](https://github.com/hot-loader/react-dom)) replaces the "react-dom" package of the same version, but with additional patches to support hot reloading.

src/configuration.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ const configuration = {
1111
// Allows SFC to be used, enables "intermediate" components used by Relay, should be disabled for Preact
1212
allowSFC: true,
1313

14+
// Allow hot reload of effect hooks
15+
hotHooks: false,
16+
1417
// Disable "hot-replacement-render"
1518
disableHotRenderer: false,
1619

src/global/generation.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import { forEachKnownClass } from '../proxy/createClassProxy';
22

3+
// this counter tracks `register` invocations.
4+
// works good, but code splitting is breaking it
35
let generation = 1;
6+
7+
// these counters are aimed to mitigate the "first render"
48
let hotComparisonCounter = 0;
59
let hotComparisonRuns = 0;
610
const nullFunction = () => ({});
11+
12+
// these callbacks would be called on component update
713
let onHotComparisonOpen = nullFunction;
814
let onHotComparisonElement = nullFunction;
915
let onHotComparisonClose = nullFunction;
1016

17+
// inversion of control
1118
export const setComparisonHooks = (open, element, close) => {
1219
onHotComparisonOpen = open;
1320
onHotComparisonElement = element;
@@ -43,12 +50,19 @@ export const configureGeneration = (counter, runs) => {
4350
hotComparisonRuns = runs;
4451
};
4552

53+
// TODO: shall it be called from incrementHotGeneration?
4654
export const enterHotUpdate = () => {
4755
Promise.resolve(incrementHot()).then(() => setTimeout(decrementHot, 0));
4856
};
4957

58+
// TODO: deprecate?
5059
export const increment = () => {
5160
enterHotUpdate();
5261
return generation++;
5362
};
5463
export const get = () => generation;
64+
65+
// These counters tracks HMR generations, and probably should be used instead of the old one
66+
let hotReplacementGeneration = 0;
67+
export const incrementHotGeneration = () => hotReplacementGeneration++;
68+
export const getHotGeneration = () => hotReplacementGeneration;

src/reactHotLoader.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
isForwardType,
88
isContextType,
99
} from './internal/reactUtils';
10-
import { increment as incrementGeneration } from './global/generation';
10+
import { increment as incrementGeneration, getHotGeneration } from './global/generation';
1111
import {
1212
updateProxyById,
1313
resetProxies,
@@ -26,6 +26,13 @@ import { hotComponentCompare } from './reconciler/componentComparator';
2626

2727
const forceSimpleSFC = { proxy: { pureSFC: true } };
2828

29+
const hookWrapper = hook => (cb, deps) => {
30+
if (configuration.hotHooks) {
31+
return hook(cb, deps ? [...deps, getHotGeneration()] : deps);
32+
}
33+
return hook(cb, deps);
34+
};
35+
2936
const reactHotLoader = {
3037
IS_REACT_MERGE_ENABLED: false,
3138
register(type, uniqueLocalName, fileName, options = {}) {
@@ -75,6 +82,7 @@ const reactHotLoader = {
7582
incrementGeneration();
7683
}
7784
if (isForwardType({ type })) {
85+
reactHotLoader.register(type.render, `${uniqueLocalName}:render`, fileName, forceSimpleSFC);
7886
updateFunctionProxyById(id, type, updateForward);
7987
incrementGeneration();
8088
}
@@ -158,6 +166,13 @@ const reactHotLoader = {
158166
React.Children.only.isPatchedByReactHotLoader = true;
159167
}
160168

169+
if (React.useEffect && !React.useState.isPatchedByReactHotLoader) {
170+
React.useEffect = hookWrapper(React.useEffect);
171+
React.useLayoutEffect = hookWrapper(React.useLayoutEffect);
172+
React.useCallback = hookWrapper(React.useCallback);
173+
React.useMemo = hookWrapper(React.useMemo);
174+
}
175+
161176
// reactHotLoader.reset()
162177
},
163178
};

src/reconciler/proxies.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import createProxy, { PROXY_KEY } from '../proxy';
22
import { resetClassProxies } from '../proxy/createClassProxy';
33
import { isCompositeComponent, isReactClass } from '../internal/reactUtils';
44
import configuration from '../configuration';
5+
import { incrementHotGeneration } from '../global/generation';
56

67
const merge = require('lodash/merge');
78

@@ -58,6 +59,8 @@ export const updateProxyById = (id, type, options = {}) => {
5859
);
5960
} else {
6061
proxiesByID[id].update(type);
62+
// proxy could be registered again only in case of HMR
63+
incrementHotGeneration();
6164
}
6265
return proxiesByID[id];
6366
};

0 commit comments

Comments
 (0)