@cleanweb/react 2.1.4 → 2.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,7 +15,81 @@ import type { TCleanState, TStateData } from './state';
15
15
  export declare class ComponentMethods<TProps extends object = {}, TState extends TStateData | null = null> {
16
16
  readonly props: TProps;
17
17
  readonly state: TState extends TStateData ? TCleanState<TState> : null;
18
+ /**
19
+ * Specify custom class members to be copied over whenever the class is
20
+ * reinstantiated during hot module replacement.
21
+ *
22
+ * Oore handles HMR by recreating the class instance
23
+ * with the updated code whenever there is a file change.
24
+ * Your component is then rerendered so that event handlers
25
+ * now point to the new functions.
26
+ *
27
+ * For this to work well, your component's state needs to be preserved,
28
+ * so it is copied over from the old instance, to the newly created one.
29
+ * This includes `state`, `props` by default, but you can
30
+ * extend it to include more properties if there are values your component expects
31
+ * to be persistent.
32
+ *
33
+ * In most case, any values you wish to preserve should be created `React.useRef`.
34
+ * ```
35
+ * // In useHooks method:
36
+ * this.inputId = useRef(inputId);
37
+ * // And access anywhere with:
38
+ * this.inputId.current;
39
+ * ```
40
+ * If you use a ref in this way, React will preserve it for you, and there will be no need
41
+ * to use `_hmrPreserveKeys`.
42
+ *
43
+ * `_hmrPreserveKeys` is only relevant in development and has not effect in production environment.
44
+ * Accordingly, you should only update this array when environment is development, so
45
+ * that it can be tree-shaken during production builds.
46
+ *
47
+ * @example Specify additional properties to be considered stateful,
48
+ * in addition to `state`, `props`, and `hooks`.
49
+ * ```ts
50
+ * MyComponentMethods extends ComponentMethods {
51
+ * // Some class member definitions...
52
+ *
53
+ * constructor() {
54
+ * if (process.env.NODE_ENV === 'development') {
55
+ * this._hmrPreserveKeys.push('inputId', 'unsubscribeCallback');
56
+ * }
57
+ * }
58
+ *
59
+ * // Method definitions...
60
+ * }
61
+ * With the above example, whenever HMR occurs, `this.inputId` and `this.unsubscribeCallback`
62
+ * will maintain there existing values, while everything else will be recreated. Meanwhile,
63
+ * because the code is written in an environment condition, it should be easy to strip it from the
64
+ * production build to avoid shipping dead code.
65
+ */
18
66
  _hmrPreserveKeys: Array<keyof this | (string & {})>;
67
+ /**
68
+ * Handle complex update logic whenever your component instance is updated through HMR.
69
+ * The function is called on the new instance, and it receives the old instance as the only argument.
70
+ * So you can access data from the old instance, and reinitialize any processes on the new instance as needed.
71
+ *
72
+ *
73
+ * `_onHmrUpdate` is only relevant in development and has not effect in production environment.
74
+ * Accordingly, you should only assign this function when environment is development, so
75
+ * that it can be tree-shaken during production builds.
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * MyComponentMethods extends ComponentMethods {
80
+ * // Some class member definitions...
81
+ *
82
+ * constructor() {
83
+ * if (process.env.NODE_ENV === 'development') {
84
+ * this._onHmrUpdate = () => {
85
+ * // Your custom hmr logic here.
86
+ * };
87
+ * }
88
+ * }
89
+ *
90
+ * // Method definitions...
91
+ * }
92
+ */
19
93
  _onHmrUpdate?: <TInstance extends this>(oldInstance: TInstance) => void;
20
94
  }
21
95
  type UseMethods = {
@@ -2,19 +2,8 @@
2
2
  /**
3
3
  * @module ComponentMethods
4
4
  */
5
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
6
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
7
- if (ar || !(i in from)) {
8
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
9
- ar[i] = from[i];
10
- }
11
- }
12
- return to.concat(ar || Array.prototype.slice.call(from));
13
- };
14
5
  Object.defineProperty(exports, "__esModule", { value: true });
15
6
  exports.useMethods = exports.ComponentMethods = void 0;
16
- // JSDoc references
17
- var helpers_1 = require("../helpers");
18
7
  // Values
19
8
  var react_1 = require("react");
20
9
  /**
@@ -29,7 +18,55 @@ var react_1 = require("react");
29
18
  */
30
19
  var ComponentMethods = /** @class */ (function () {
31
20
  function ComponentMethods() {
32
- this._hmrPreserveKeys = [];
21
+ /**
22
+ * Specify custom class members to be copied over whenever the class is
23
+ * reinstantiated during hot module replacement.
24
+ *
25
+ * Oore handles HMR by recreating the class instance
26
+ * with the updated code whenever there is a file change.
27
+ * Your component is then rerendered so that event handlers
28
+ * now point to the new functions.
29
+ *
30
+ * For this to work well, your component's state needs to be preserved,
31
+ * so it is copied over from the old instance, to the newly created one.
32
+ * This includes `state`, `props` by default, but you can
33
+ * extend it to include more properties if there are values your component expects
34
+ * to be persistent.
35
+ *
36
+ * In most case, any values you wish to preserve should be created `React.useRef`.
37
+ * ```
38
+ * // In useHooks method:
39
+ * this.inputId = useRef(inputId);
40
+ * // And access anywhere with:
41
+ * this.inputId.current;
42
+ * ```
43
+ * If you use a ref in this way, React will preserve it for you, and there will be no need
44
+ * to use `_hmrPreserveKeys`.
45
+ *
46
+ * `_hmrPreserveKeys` is only relevant in development and has not effect in production environment.
47
+ * Accordingly, you should only update this array when environment is development, so
48
+ * that it can be tree-shaken during production builds.
49
+ *
50
+ * @example Specify additional properties to be considered stateful,
51
+ * in addition to `state`, `props`, and `hooks`.
52
+ * ```ts
53
+ * MyComponentMethods extends ComponentMethods {
54
+ * // Some class member definitions...
55
+ *
56
+ * constructor() {
57
+ * if (process.env.NODE_ENV === 'development') {
58
+ * this._hmrPreserveKeys.push('inputId', 'unsubscribeCallback');
59
+ * }
60
+ * }
61
+ *
62
+ * // Method definitions...
63
+ * }
64
+ * With the above example, whenever HMR occurs, `this.inputId` and `this.unsubscribeCallback`
65
+ * will maintain there existing values, while everything else will be recreated. Meanwhile,
66
+ * because the code is written in an environment condition, it should be easy to strip it from the
67
+ * production build to avoid shipping dead code.
68
+ */
69
+ this._hmrPreserveKeys = []; // @todo Keep undefined. Update to empty array after instantiation in dev env.
33
70
  }
34
71
  return ComponentMethods;
35
72
  }());
@@ -42,11 +79,12 @@ exports.ComponentMethods = ComponentMethods;
42
79
  * `state` should be an instance of `CleanState` created with {@link useCleanState}.
43
80
  */
44
81
  var useMethods = function () {
82
+ var _a;
45
83
  var args = [];
46
84
  for (var _i = 0; _i < arguments.length; _i++) {
47
85
  args[_i] = arguments[_i];
48
86
  }
49
- var Methods = args[0], _a = args[1], props = _a === void 0 ? {} : _a, state = args[2];
87
+ var Methods = args[0], _b = args[1], props = _b === void 0 ? {} : _b, state = args[2];
50
88
  // Vite HMR seems to sometimes reinitialize useMemo calls after a hot update,
51
89
  // causing the instance to be unexpectedly recreated in the middle of the component's lifecycle.
52
90
  // But useRef and useState values appear to always be preserved whenever this happens.
@@ -56,55 +94,32 @@ var useMethods = function () {
56
94
  // latestInstance is only extracted into a separate variable for use in dev mode during HMR.
57
95
  var latestInstance = (0, react_1.useMemo)(function () { return new Methods(); }, [Methods]);
58
96
  var instanceRef = (0, react_1.useRef)(latestInstance);
59
- if (process.env.NODE_ENV === 'development') {
60
- var rerender_1 = (0, helpers_1.useRerender)();
61
- (0, react_1.useEffect)(function () {
62
- var _a;
63
- if (instanceRef.current === latestInstance)
64
- return;
65
- console.log([
66
- 'HMR-updated component class detected.',
67
- 'Creating a new instance with the updated class.',
68
- 'All stateful values will be copied over.\n\n',
69
- 'Note that this mechanism only works in the `development` environment during HMR.',
70
- 'In production, the class argument will be ignored after the first render.\n\n',
71
- 'If this wasn\'t an HMR update, you should refactor your code to make sure',
72
- 'all clean-react hooks receive the same class argument on every render.'
73
- ].join(' '));
74
- var oldInstance = instanceRef.current;
75
- var hmrPreserveKeys = __spreadArray(__spreadArray([], latestInstance._hmrPreserveKeys, true), [
76
- 'state', 'props',
77
- ], false);
78
- hmrPreserveKeys.forEach(function (_key) {
79
- var key = _key;
80
- // @ts-expect-error We're assigning to readonly properties. Also, Typescript doesn't know that the type of the left and right side will always match, due to the dynamic access.
81
- latestInstance[key] = oldInstance[key];
82
- });
83
- (_a = latestInstance._onHmrUpdate) === null || _a === void 0 ? void 0 : _a.call(latestInstance, oldInstance);
84
- Reflect.ownKeys(oldInstance).forEach(function (_key) {
85
- var key = _key;
86
- delete oldInstance[key];
87
- });
88
- Object.setPrototypeOf(oldInstance, latestInstance);
89
- instanceRef.current = latestInstance;
90
- rerender_1();
97
+ var refreshState = function () {
98
+ // @ts-expect-error
99
+ instanceRef.current.props = props;
100
+ // @ts-expect-error
101
+ if (state)
102
+ instanceRef.current.state = state;
103
+ };
104
+ if (process.env.NODE_ENV === 'development' && instanceRef.current !== latestInstance) {
105
+ var oldInstance_1 = instanceRef.current;
106
+ latestInstance._hmrPreserveKeys.forEach(function (_key) {
107
+ var key = _key;
108
+ // @ts-expect-error We're assigning to readonly properties. Also, Typescript doesn't know that the type of the left and right side will always match, due to the dynamic access.
109
+ latestInstance[key] = oldInstance_1[key];
110
+ });
111
+ Reflect.ownKeys(oldInstance_1).forEach(function (_key) {
112
+ var key = _key;
113
+ delete oldInstance_1[key];
91
114
  });
115
+ Object.setPrototypeOf(oldInstance_1, latestInstance);
116
+ instanceRef.current = latestInstance;
117
+ refreshState();
118
+ (_a = latestInstance._onHmrUpdate) === null || _a === void 0 ? void 0 : _a.call(latestInstance, oldInstance_1);
92
119
  }
93
- var methods = instanceRef.current;
94
- /**
95
- * A proxy variable to allow typechecking of the assignment
96
- * to a readonly property,
97
- * despite the need for "readonly" error suppression.
98
- */
99
- var _propsProxy;
100
- /** @see {@link _propsProxy} */
101
- var _stateProxy;
102
- // @ts-expect-error
103
- methods.props = (_propsProxy = props);
104
- // @ts-expect-error
105
- if (state)
106
- methods.state = (_stateProxy = state);
107
- return methods;
120
+ else
121
+ refreshState();
122
+ return instanceRef.current;
108
123
  };
109
124
  exports.useMethods = useMethods;
110
125
  /** /testing: {
@@ -55,7 +55,81 @@ export declare class ComponentLogic<TProps extends TPropsBase = null> {
55
55
  * your component class.
56
56
  */
57
57
  useHooks: () => object | void;
58
+ /**
59
+ * Specify custom class members to be copied over whenever the class is
60
+ * reinstantiated during hot module replacement.
61
+ *
62
+ * Oore handles HMR by recreating the class instance
63
+ * with the updated code whenever there is a file change.
64
+ * Your component is then rerendered so that event handlers
65
+ * now point to the new functions.
66
+ *
67
+ * For this to work well, your component's state needs to be preserved,
68
+ * so it is copied over from the old instance, to the newly created one.
69
+ * This includes `state`, `props` by default, but you can
70
+ * extend it to include more properties if there are values your component expects
71
+ * to be persistent.
72
+ *
73
+ * In most case, any values you wish to preserve should be created `React.useRef`.
74
+ * ```
75
+ * // In useHooks method:
76
+ * this.inputId = useRef(inputId);
77
+ * // And access anywhere with:
78
+ * this.inputId.current;
79
+ * ```
80
+ * If you use a ref in this way, React will preserve it for you, and there will be no need
81
+ * to use `_hmrPreserveKeys`.
82
+ *
83
+ * `_hmrPreserveKeys` is only relevant in development and has not effect in production environment.
84
+ * Accordingly, you should only update this array when environment is development, so
85
+ * that it can be tree-shaken during production builds.
86
+ *
87
+ * @example Specify additional properties to be considered stateful,
88
+ * in addition to `state`, `props`, and `hooks`.
89
+ * ```ts
90
+ * MyComponentMethods extends ComponentMethods {
91
+ * // Some class member definitions...
92
+ *
93
+ * constructor() {
94
+ * if (process.env.NODE_ENV === 'development') {
95
+ * this._hmrPreserveKeys.push('inputId', 'unsubscribeCallback');
96
+ * }
97
+ * }
98
+ *
99
+ * // Method definitions...
100
+ * }
101
+ * With the above example, whenever HMR occurs, `this.inputId` and `this.unsubscribeCallback`
102
+ * will maintain there existing values, while everything else will be recreated. Meanwhile,
103
+ * because the code is written in an environment condition, it should be easy to strip it from the
104
+ * production build to avoid shipping dead code.
105
+ */
58
106
  _hmrPreserveKeys: Array<keyof this | (string & {})>;
107
+ /**
108
+ * Handle complex update logic whenever your component instance is updated through HMR.
109
+ * The function is called on the new instance, and it receives the old instance as the only argument.
110
+ * So you can access data from the old instance, and reinitialize any processes on the new instance as needed.
111
+ *
112
+ *
113
+ * `_onHmrUpdate` is only relevant in development and has not effect in production environment.
114
+ * Accordingly, you should only assign this function when environment is development, so
115
+ * that it can be tree-shaken during production builds.
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * MyComponentMethods extends ComponentMethods {
120
+ * // Some class member definitions...
121
+ *
122
+ * constructor() {
123
+ * if (process.env.NODE_ENV === 'development') {
124
+ * this._onHmrUpdate = () => {
125
+ * // Your custom hmr logic here.
126
+ * };
127
+ * }
128
+ * }
129
+ *
130
+ * // Method definitions...
131
+ * }
132
+ */
59
133
  _onHmrUpdate?: <TInstance extends this>(oldInstance: TInstance) => void;
60
134
  }
61
135
  /**
@@ -1,13 +1,4 @@
1
1
  "use strict";
2
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
3
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
4
- if (ar || !(i in from)) {
5
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
6
- ar[i] = from[i];
7
- }
8
- }
9
- return to.concat(ar || Array.prototype.slice.call(from));
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  exports.useLogic = exports.ComponentLogic = void 0;
13
4
  var react_1 = require("react");
@@ -43,6 +34,54 @@ var ComponentLogic = /** @class */ (function () {
43
34
  * your component class.
44
35
  */
45
36
  this.useHooks = function () { };
37
+ /**
38
+ * Specify custom class members to be copied over whenever the class is
39
+ * reinstantiated during hot module replacement.
40
+ *
41
+ * Oore handles HMR by recreating the class instance
42
+ * with the updated code whenever there is a file change.
43
+ * Your component is then rerendered so that event handlers
44
+ * now point to the new functions.
45
+ *
46
+ * For this to work well, your component's state needs to be preserved,
47
+ * so it is copied over from the old instance, to the newly created one.
48
+ * This includes `state`, `props` by default, but you can
49
+ * extend it to include more properties if there are values your component expects
50
+ * to be persistent.
51
+ *
52
+ * In most case, any values you wish to preserve should be created `React.useRef`.
53
+ * ```
54
+ * // In useHooks method:
55
+ * this.inputId = useRef(inputId);
56
+ * // And access anywhere with:
57
+ * this.inputId.current;
58
+ * ```
59
+ * If you use a ref in this way, React will preserve it for you, and there will be no need
60
+ * to use `_hmrPreserveKeys`.
61
+ *
62
+ * `_hmrPreserveKeys` is only relevant in development and has not effect in production environment.
63
+ * Accordingly, you should only update this array when environment is development, so
64
+ * that it can be tree-shaken during production builds.
65
+ *
66
+ * @example Specify additional properties to be considered stateful,
67
+ * in addition to `state`, `props`, and `hooks`.
68
+ * ```ts
69
+ * MyComponentMethods extends ComponentMethods {
70
+ * // Some class member definitions...
71
+ *
72
+ * constructor() {
73
+ * if (process.env.NODE_ENV === 'development') {
74
+ * this._hmrPreserveKeys.push('inputId', 'unsubscribeCallback');
75
+ * }
76
+ * }
77
+ *
78
+ * // Method definitions...
79
+ * }
80
+ * With the above example, whenever HMR occurs, `this.inputId` and `this.unsubscribeCallback`
81
+ * will maintain there existing values, while everything else will be recreated. Meanwhile,
82
+ * because the code is written in an environment condition, it should be easy to strip it from the
83
+ * production build to avoid shipping dead code.
84
+ */
46
85
  this._hmrPreserveKeys = [];
47
86
  }
48
87
  return ComponentLogic;
@@ -58,64 +97,47 @@ exports.ComponentLogic = ComponentLogic;
58
97
  * @see https://cleanjsweb.github.io/neat-react/functions/API.useLogic.html
59
98
  */
60
99
  var useLogic = function () {
61
- var _a, _b;
100
+ var _a;
62
101
  var args = [];
63
102
  for (var _i = 0; _i < arguments.length; _i++) {
64
103
  args[_i] = arguments[_i];
65
104
  }
66
- var Logic = args[0], _c = args[1], props = _c === void 0 ? {} : _c;
105
+ var Logic = args[0], _b = args[1], props = _b === void 0 ? {} : _b;
67
106
  // In production, we only use the latestInstance the first time, and it's ignored every other time.
68
107
  // This means changing the class at runtime will have no effect in production.
69
108
  // latestInstance is only extracted into a separate variable for use in dev mode during HMR.
70
109
  var latestInstance = (0, react_1.useMemo)(function () { return new Logic(); }, [Logic]);
110
+ // const latestInstance = useMemo(() => new Logic(), []);
71
111
  var instanceRef = (0, react_1.useRef)(latestInstance);
72
- if (process.env.NODE_ENV === 'development') {
73
- if (instanceRef.current !== latestInstance) {
74
- console.log([
75
- 'HMR-updated component class detected.',
76
- 'Creating a new instance with the updated class.',
77
- 'All stateful values will be copied over.\n\n',
78
- 'Note that this mechanism only works in the `development` environment during HMR.',
79
- 'In production, the class argument will be ignored after the first render.\n\n',
80
- 'If this wasn\'t an HMR update, you should refactor your code to make sure',
81
- 'all clean-react hooks receive the same class argument on every render.'
82
- ].join(' '));
83
- var oldInstance_1 = instanceRef.current;
84
- var hmrPreserveKeys = __spreadArray(__spreadArray([], latestInstance._hmrPreserveKeys, true), [
85
- 'state', 'props', 'hooks',
86
- ], false);
87
- hmrPreserveKeys.forEach(function (_key) {
88
- var key = _key;
89
- // @ts-expect-error We're assigning to readonly properties. Also, Typescript doesn't know that the type of the left and right side will always match, due to the dynamic access.
90
- latestInstance[key] = oldInstance_1[key];
91
- });
92
- (_a = latestInstance._onHmrUpdate) === null || _a === void 0 ? void 0 : _a.call(latestInstance, oldInstance_1);
93
- instanceRef.current = latestInstance;
94
- Reflect.ownKeys(oldInstance_1).forEach(function (_key) {
95
- var key = _key;
96
- delete oldInstance_1[key];
97
- });
98
- Object.setPrototypeOf(oldInstance_1, latestInstance);
99
- }
112
+ var refreshState = function () {
113
+ var _a;
114
+ // @ts-expect-error
115
+ instanceRef.current.props = props;
116
+ // @ts-expect-error
117
+ instanceRef.current.state = (0, state_1.useCleanState)(instanceRef.current.getInitialState, props);
118
+ // @ts-expect-error
119
+ instanceRef.current.hooks = (_a = instanceRef.current.useHooks()) !== null && _a !== void 0 ? _a : {};
120
+ };
121
+ if (process.env.NODE_ENV === 'development' && instanceRef.current !== latestInstance) {
122
+ var oldInstance_1 = instanceRef.current;
123
+ latestInstance._hmrPreserveKeys.forEach(function (_key) {
124
+ var key = _key;
125
+ // @ts-expect-error We're assigning to readonly properties. Also, Typescript doesn't know that the type of the left and right side will always match, due to the dynamic access.
126
+ latestInstance[key] = oldInstance_1[key];
127
+ });
128
+ Reflect.ownKeys(oldInstance_1).forEach(function (_key) {
129
+ var key = _key;
130
+ delete oldInstance_1[key];
131
+ });
132
+ Object.setPrototypeOf(oldInstance_1, latestInstance);
133
+ instanceRef.current = latestInstance;
134
+ refreshState();
135
+ (_a = latestInstance._onHmrUpdate) === null || _a === void 0 ? void 0 : _a.call(latestInstance, oldInstance_1);
100
136
  }
101
- var self = instanceRef.current;
102
- /**
103
- * A proxy variable to allow typechecking of the assignment
104
- * to a readonly property,
105
- * despite the need for "readonly" error suppression.
106
- */
107
- var _propsProxy;
108
- /** @see {@link _propsProxy} */
109
- var _stateProxy;
110
- /** @see {@link _propsProxy} */
111
- var _hooksProxy;
112
- // @ts-expect-error
113
- self.props = (_propsProxy = props);
114
- // @ts-expect-error
115
- self.state = (_stateProxy = (0, state_1.useCleanState)(self.getInitialState, props));
116
- // @ts-expect-error
117
- self.hooks = (_hooksProxy = (_b = self.useHooks()) !== null && _b !== void 0 ? _b : {});
118
- return self;
137
+ else
138
+ refreshState();
139
+ return instanceRef.current;
140
+ ;
119
141
  };
120
142
  exports.useLogic = useLogic;
121
143
  /** /
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleanweb/react",
3
- "version": "2.1.4",
3
+ "version": "2.1.6",
4
4
  "description": "A suite of helpers for writing cleaner React function components.",
5
5
  "engines": {
6
6
  "node": ">=18"