@cleanweb/react 1.1.1-beta.8 → 2.0.1-beta.0

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.
Files changed (64) hide show
  1. package/README.md +242 -187
  2. package/README.old.md +342 -0
  3. package/build/base/merged-state.d.ts +1 -0
  4. package/build/base/merged-state.js +3 -2
  5. package/build/base/methods.d.ts +40 -7
  6. package/build/base/methods.js +45 -81
  7. package/build/base/state/class-types.d.ts +17 -0
  8. package/build/base/state/class-types.js +2 -0
  9. package/build/base/state/class.d.ts +14 -0
  10. package/build/base/{state.js → state/class.js} +4 -55
  11. package/build/base/state/hook-types.d.ts +11 -0
  12. package/build/base/state/hook-types.js +2 -0
  13. package/build/base/state/hooks.d.ts +11 -0
  14. package/build/base/state/hooks.js +57 -0
  15. package/build/base/state/index.d.ts +4 -0
  16. package/build/base/state/index.js +9 -0
  17. package/build/classy/class/index.d.ts +102 -0
  18. package/build/classy/class/index.js +147 -0
  19. package/build/classy/class/types/class/instance.d.ts +12 -0
  20. package/build/classy/class/types/class/instance.js +2 -0
  21. package/build/classy/class/types/class/static.d.ts +9 -0
  22. package/build/classy/class/types/class/static.js +12 -0
  23. package/build/classy/class/types/extractor.d.ts +6 -0
  24. package/build/classy/class/types/extractor.js +2 -0
  25. package/build/classy/class/utils/function-name.d.ts +2 -0
  26. package/build/classy/class/utils/function-name.js +17 -0
  27. package/build/classy/class/utils/rerender.d.ts +1 -0
  28. package/build/classy/class/utils/rerender.js +11 -0
  29. package/build/classy/class/utils/use-component/index.d.ts +6 -0
  30. package/build/classy/class/utils/use-component/index.js +17 -0
  31. package/build/classy/class/utils/use-component/types.d.ts +22 -0
  32. package/build/classy/class/utils/use-component/types.js +2 -0
  33. package/build/classy/instance/index.d.ts +94 -0
  34. package/build/classy/{instance.js → instance/index.js} +62 -33
  35. package/build/classy/instance/mount-callbacks.d.ts +4 -0
  36. package/build/classy/instance/mount-callbacks.js +30 -0
  37. package/build/classy/instance/types/hook.d.ts +16 -0
  38. package/build/classy/instance/types/hook.js +2 -0
  39. package/build/classy/instance/types/instance.d.ts +11 -0
  40. package/build/classy/instance/types/instance.js +2 -0
  41. package/build/classy/instance/types/static.d.ts +11 -0
  42. package/build/classy/instance/types/static.js +17 -0
  43. package/build/classy/logic/index.d.ts +42 -0
  44. package/build/classy/logic/index.js +122 -0
  45. package/build/classy/logic/types/hook.d.ts +24 -0
  46. package/build/classy/logic/types/hook.js +2 -0
  47. package/build/classy/logic/types/instance.d.ts +18 -0
  48. package/build/classy/logic/types/instance.js +2 -0
  49. package/build/classy/logic/types/static.d.ts +17 -0
  50. package/build/classy/logic/types/static.js +2 -0
  51. package/build/globals.d.ts +23 -33
  52. package/build/globals.js +2 -20
  53. package/build/index.d.ts +1 -1
  54. package/build/index.js +2 -1
  55. package/build/tsconfig.json +3 -0
  56. package/package.json +8 -6
  57. package/build/base/state.d.ts +0 -29
  58. package/build/classy/class.d.ts +0 -22
  59. package/build/classy/class.js +0 -118
  60. package/build/classy/instance.d.ts +0 -60
  61. package/build/classy/logic.d.ts +0 -20
  62. package/build/classy/logic.js +0 -79
  63. package/build/globals.private.d.ts +0 -44
  64. package/build/globals.private.js +0 -34
package/README.old.md ADDED
@@ -0,0 +1,342 @@
1
+ # Structured & Cleaner React Function Components
2
+
3
+ ## Quick Start
4
+ This package provides a suite of tools for writing cleaner React function components. It is particularly useful for larger components with lots of state variables and multiple closure functions that need to access those variables. The most likely use cases will use one of the three main exported members.
5
+
6
+ > The following sections will use examples to demonstrate how each tool can be used. These example are intended to be read as a before and after comparison. They are all based on the same "before" code, which is shown below:
7
+
8
+
9
+ ### Our Sample React Function Component
10
+ ```jsx
11
+ const Footer = (props) => {
12
+ const [state1, setState1] = useState(props.defaultValue);
13
+ const [state2, setState2] = useState();
14
+ const [label, setLabel] = useState('Click me');
15
+ const [submitted, setSubmitted] = useState(false);
16
+
17
+ const [store, updateStore] = useGlobalStore();
18
+
19
+ const { param } = props;
20
+
21
+
22
+ // Required to run once *before* the component mounts.
23
+ const paragraphs = useMemo(() => getValue(param), [param]);
24
+
25
+
26
+ /** @todo Use `useSyncExternalStore`. */
27
+ const subscribeToExternalDataSource = useCallback(() => {
28
+ externalDataSource.subscribe((data) => {
29
+ setLabel(data.label);
30
+ });
31
+ }, [setLabel]);
32
+
33
+ // Required to run once *after* the component mounts.
34
+ useEffect(subscribeToExternalDataSource, []);
35
+ useEffect(() => {
36
+ const onWindowResize = () => {};
37
+ window.addEventListener('resize', onWindowResize);
38
+
39
+ return () => {
40
+ window.removeEventListener('resize', onWindowResize);
41
+ };
42
+ }, []);
43
+
44
+ // Run *after* every render.
45
+ useEffect(() => {
46
+ doSomething();
47
+ return () => {};
48
+ })
49
+
50
+ const submit = useCallback(() => {
51
+ sendData(state1, state2);
52
+ setSubmitted(true);
53
+ }, [state1]); // Notice how `state2` above could easily be stale by the time the callback runs.
54
+
55
+
56
+ // Run before every render.
57
+ const text = `${label}, please.`;
58
+
59
+ return (
60
+ <footer>
61
+ {paragraphs ? paragraphs.map((copy) => (
62
+ <p>{copy}</p>
63
+ )) : null}
64
+
65
+ <button onClick={submit}>
66
+ {text}
67
+ </button>
68
+ </footer>
69
+ );
70
+ };
71
+
72
+ export default Footer;
73
+ // Or use in JSX as `<Footer />`.
74
+ ```
75
+
76
+
77
+ ### A Cleaner State Management API
78
+ The `useCleanState` hook provides a cleaner API for working with state, particularly in components with a relatively high number of state variables. It allows you to manage multiple state variables as a single unit. You will likely find this cleaner to work with than having multiple local variables for each state. The example below demonstrates the use of this hook.
79
+
80
+
81
+ ### Extracting and Structuring Component Logic
82
+ The `useLogic` hook allows you to write your component's logic outside the function component's body, and helps you keep them all better organized. It also provides a much cleaner API for working with multiple state variables. Here's what a function component looks like with the `useLogic` hook.
83
+
84
+ **Before**
85
+ [See the "before" code above](#our-sample-react-function-component).
86
+
87
+ **After**
88
+ ```jsx
89
+ class FooterLogic {
90
+ static getInitialState = (props) => {
91
+ return {
92
+ state1: props.defaultValue,
93
+ state2: undefined,
94
+ label: 'Click me',
95
+ submitted: false,
96
+ };
97
+ };
98
+
99
+ subscribeToExternalDataSource = () => {
100
+ externalDataSource.subscribe((data) => {
101
+ this.state.label = data.label;
102
+ });
103
+ }
104
+
105
+ useHooks = () => {
106
+ const [store, updateStore] = useGlobalStore();
107
+ const { param } = this.props;
108
+
109
+ // Required to run once *before* the component mounts.
110
+ const paragraphs = useMemo(() => getValue(param), [param]);
111
+
112
+ // Required to run once *after* the component mounts.
113
+ useEffect(this.subscribeToExternalDataSource, []);
114
+ useEffect(() => {
115
+ const onWindowResize = () => {};
116
+ window.addEventListener('resize', onWindowResize);
117
+
118
+ return () => {
119
+ window.removeEventListener('resize', onWindowResize);
120
+ };
121
+ }, []);
122
+
123
+ // Run *after* every render.
124
+ useEffect(() => {
125
+ doSomething();
126
+ return () => {};
127
+ });
128
+
129
+ return {
130
+ store,
131
+ updateStore,
132
+ paragraphs,
133
+ };
134
+ };
135
+
136
+ submit = () => {
137
+ const { state1, state2 } = this.state;
138
+ sendData(state1, state2);
139
+ this.state.submitted = true;
140
+ }; // `state2` is never stale. All `state` and `props` references return the latest value. No extra steps required.
141
+ }
142
+
143
+ // Footer Template
144
+ const Footer = (props) => {
145
+ const self = useLogic(FooterLogic, props);
146
+
147
+ const { paragraphs } = self.hooks;
148
+ const { state } = self;
149
+
150
+ // Run before every render.
151
+ const text = `${state.label}, please.`;
152
+
153
+ return (
154
+ <footer>
155
+ {paragraphs ? paragraphs.map((copy) => (
156
+ <p>{copy}</p>
157
+ )) : null}
158
+
159
+ <button onClick={self.submit}>
160
+ {text}
161
+ </button>
162
+ </footer>
163
+ );
164
+ };
165
+
166
+ export default Footer;
167
+ // Or use in JSX as `<Footer />`.
168
+ ```
169
+
170
+ The `useLogic` hook combines the functionality of two base hooks which can also be used directly. They are [`useCleanState`](https://cleanjsweb.github.io/neat-react/clean-state/index) and [`useMethods`](https://cleanjsweb.github.io/neat-react/methods/index). `useCleanState` can be used independently if you only want a cleaner state management API. `useMethods` is designed to be used together with `useCleanState`, but rather than calling both individually, you may find it more convenient to use `useLogic`, which combines both as well as the added functionality of the `useHooks` method.
171
+
172
+ > It is possible to have multiple calls to `useLogic` in the same component. This allows your function component template to consume state and logic from multiple sources, or it can simply be used to group distinct pieces of related logic into separate classes.
173
+
174
+ For a fuller discussion of how `useLogic` works, start at the [clean-state documentation](https://cleanjsweb.github.io/neat-react/clean-state/index).
175
+ For an API reference, see the [API reference](https://cleanjsweb.github.io/neat-react/logic/api).
176
+
177
+
178
+ ### Working With Lifecycle, and Migrating From a React.Component Class to a Function Component
179
+ In addition to having cleaner and more structured component logic, you can also simplify the process of working with your component's lifecycle with the final two exported members. The `useInstance` hook builds on the functionality of `useLogic` and adds lifecyle methods to the class. This means the class can now be thought of as truly representing a single instance of a React component. The `ClassComponent` class extends this to its fullest by allowing you to write the function component itself as a method within the class, and removing the need to explicitly call `useInstance`.
180
+
181
+ **Before**
182
+ [See the "before" code above](#our-sample-react-function-component).
183
+
184
+ **After**
185
+ ```jsx
186
+ class Footer extends ClassComponent {
187
+ // Call the static method extract() to retrieve the renderer for your class.
188
+ // This is a function that you can render like any other function component.
189
+ // Each instance of the renderer in your component tree will create its own instance of the class.
190
+ static readonly RC = Button.extract();
191
+
192
+ static getInitialState = (props) => {
193
+ return {
194
+ state1: props.defaultValue,
195
+ state2: undefined,
196
+ label: 'Click me',
197
+ submitted: false,
198
+ };
199
+ };
200
+
201
+ useHooks = () => {
202
+ const [store, updateStore] = useGlobalStore();
203
+ return { store, updateStore };
204
+ }
205
+
206
+ /***************************
207
+ * New Lifecycle Methods *
208
+ ***************************/
209
+
210
+ beforeMount = () => {
211
+ this.paragraphs = getValue();
212
+ }
213
+
214
+ // Run after the component is mounted.
215
+ onMount = () => {
216
+ this.subscribeToExternalDataSource();
217
+ window.addEventListener('resize', this.onWindowResize);
218
+
219
+ // Return cleanup callback.
220
+ return () => {
221
+ window.removeEventListener('resize', this.onWindowResize);
222
+ };
223
+ }
224
+
225
+ beforeRender = () => {
226
+ this.text = `${label}, please.`;
227
+ }
228
+
229
+ // Run after every render.
230
+ onRender = () => {
231
+ doSomething();
232
+
233
+ // Return cleanup callback.
234
+ return () => {};
235
+ }
236
+
237
+ cleanUp = () => {
238
+ // Run some non-mount-related cleanup when the component dismounts.
239
+ // onMount (and onRender) returns its own cleanup function.
240
+ }
241
+
242
+ /***************************
243
+ * [End] Lifecycle Methods *
244
+ ***************************/
245
+
246
+ submit = () => {
247
+ // Methods are guaranteed to have access to the most recent state values.
248
+ const { state1, state2 } = this.state;
249
+
250
+ sendData(state1, state2);
251
+
252
+ // CleanState uses JavaScript's getters and setters, allowing you to assign state values directly.
253
+ // The effect is the same as if you called the setter function, which is available through `state.put.submitted(true)`.
254
+ this.state.submitted = true;
255
+ }
256
+
257
+ onWindowResize = () => {};
258
+
259
+ subscribeToExternalDataSource = () => {
260
+ const unsubscribe = externalDataSource.subscribe((data) => {
261
+ this.state.label = data.label;
262
+ });
263
+
264
+ return unsubscribe;
265
+ }
266
+
267
+ /** You can also separate out discreet chunks of your UI template. */
268
+ Paragraphs = () => {
269
+ if (!this.paragraphs) return null;
270
+
271
+ return this.paragraphs.map((content, index) => (
272
+ <p key={index}>
273
+ {content || this.state.label}
274
+ </p>
275
+ ));
276
+ }
277
+
278
+ /** Button Template */
279
+ template = () => {
280
+ const { Paragraphs, submit, state } = this;
281
+
282
+ return (
283
+ <Fragment>
284
+ <Paragraphs />
285
+
286
+ {/* You can access the setter functions returned from useState through the state.put object. */}
287
+ {/* This is more convenient than the assignment approach if you need to pass a setter as a callback. */}
288
+ {/* Use state.putMany to set multiple values at once. It works just like setState in React.Component classes. */}
289
+ {/* e.g state.inputValue = 'foo', or state.put.inputValue('foo'), or state.putMany({ inputValue: 'foo' }) */}
290
+ <CustomInput setValue={state.put.inputValue} />
291
+
292
+ <button onClick={submit}>
293
+ {this.text}
294
+ </button>
295
+ </Fragment>
296
+ );
297
+ }
298
+ }
299
+
300
+ // Can also be used directly in JSX as `<Button.RC />`.
301
+ export default Button.RC;
302
+ ```
303
+
304
+ > If you would like to keep the actual function component separate and call `useInstance` directly, see the [`useInstance` docs](https://cleanjsweb.github.io/neat-react/instance/index) for more details and examples.
305
+
306
+ At its core, any component you write with `ClassComponent` is still just a React function component, with some supporting logic around it. This has the added advantage of making it significantly easier to migrate class components written with `React.Component` to the newer hooks-based function components, while still maintaining the overall structure of a class component, and the advantages that the class component approach provided.
307
+
308
+ For a fuller discussion of how this works, start at the [`useInstance` documentation](https://cleanjsweb.github.io/neat-react/instance/index).
309
+ For more details on the lifecycle methods and other API reference, see the [`ClassComponent` API docs](https://cleanjsweb.github.io/neat-react/class-component/api).
310
+
311
+ ### The `<Use>` Component
312
+ If you only want to use hooks in your `React.Component` class without having to refactor anything, use the [`Use` component](https://cleanjsweb.github.io/neat-react/class-component/index#the-use-component).
313
+
314
+ ```jsx
315
+ class Button extends React.Component {
316
+ handleGlobalStore = ([store, updateStore]) => {
317
+ this.setState({ userId: store.userId });
318
+ this.store = store;
319
+ this.updateStore = updateStore;
320
+ }
321
+
322
+ UseHooks = () => {
323
+ return <>
324
+ <Use hook={useGlobalStore}
325
+ onUpdate={handleGlobalStore}
326
+ argumentsList={[]}
327
+ key="useGlobalStore"
328
+ />
329
+ </>;
330
+ }
331
+
332
+ render() {
333
+ const { UseHooks } = this;
334
+
335
+ return <>
336
+ <UseHooks />
337
+
338
+ <button>Click me</button>
339
+ </>;
340
+ }
341
+ }
342
+ ```
@@ -1,3 +1,4 @@
1
+ import '../globals';
1
2
  declare class MergedState<TState extends object> {
2
3
  static useRefresh<TState extends object>(this: MergedState<TState>): void;
3
4
  reservedKeys: string[];
@@ -12,6 +12,7 @@ var __assign = (this && this.__assign) || function () {
12
12
  };
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.useMergedState = void 0;
15
+ require("../globals");
15
16
  var react_1 = require("react");
16
17
  var MergedState = /** @class */ (function () {
17
18
  function MergedState(initialState) {
@@ -70,9 +71,9 @@ var MergedState = /** @class */ (function () {
70
71
  return MergedState;
71
72
  }());
72
73
  var useMergedState = function (initialState) {
73
- var cleanState = (0, react_1.useMemo)(function () {
74
+ var cleanState = (0, react_1.useRef)((0, react_1.useMemo)(function () {
74
75
  return new MergedState(initialState);
75
- }, []);
76
+ }, [])).current;
76
77
  MergedState.useRefresh.call(cleanState);
77
78
  return cleanState;
78
79
  };
@@ -1,8 +1,41 @@
1
- import type { TCleanState } from './state';
2
- export declare class ComponentMethods<TProps extends object, TState extends object> {
3
- props: TProps;
4
- state: TCleanState<TState>;
1
+ import type { TCleanState, TStateData } from './state';
2
+ /**
3
+ * Base class for a class that holds methods intended for use in a function component.
4
+ * These methods will have access to the components state and props via
5
+ * `this.state` and `this.props` respectively.
6
+ *
7
+ * Call the {@link useMethods} hook inside your function component to instantiate the class.
8
+ */
9
+ export declare class ComponentMethods<TProps extends object = {}, TState extends TStateData | null = null> {
10
+ readonly props: TProps;
11
+ state: TState extends TStateData ? TCleanState<TState> : null;
5
12
  }
6
- type UseMethods = <Class extends typeof ComponentMethods<object, object>>(Methods: Class & Constructor<InstanceType<Class>>, props: InstanceType<Class>['props'], state: InstanceType<Class>['state']) => InstanceType<Class>;
7
- export declare const useMethods: UseMethods;
8
- export {};
13
+ type UseMethods = {
14
+ <Class extends typeof ComponentMethods<object, object>>(Methods: Class & Constructor<InstanceType<Class>>, props: InstanceType<Class>['props'], state: InstanceType<Class>['state']): InstanceType<Class>;
15
+ <Class extends typeof ComponentMethods<object, null>>(Methods: Class & Constructor<InstanceType<Class>>, props: InstanceType<Class>['props'], state?: null): InstanceType<Class>;
16
+ <Class extends typeof ComponentMethods<HardEmptyObject, null>>(Methods: Class & Constructor<InstanceType<Class>>): InstanceType<Class>;
17
+ };
18
+ /**
19
+ * Returns an instance of the provided class,
20
+ * with the state and props arguments added as instance members.
21
+ *
22
+ * `state` must be an instance of `CleanState` created with {@link useCleanState}.
23
+ */
24
+ declare const useMethods: UseMethods;
25
+ export { useMethods };
26
+ /** /testing: {
27
+ let a = async () => {
28
+ const a: object = {b: ''};
29
+
30
+ type t = keyof typeof a;
31
+
32
+ class MyMethods extends ComponentMethods<WeakEmptyObject, null> {
33
+ // static getInitialState = () => ({});
34
+ };
35
+
36
+ const { useCleanState } = (await import('./state.js'));
37
+
38
+ const self = useMethods(MyMethods, {});
39
+ self.state;
40
+ }
41
+ }/**/
@@ -1,58 +1,15 @@
1
1
  "use strict";
2
- var __extends = (this && this.__extends) || (function () {
3
- var extendStatics = function (d, b) {
4
- extendStatics = Object.setPrototypeOf ||
5
- ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
- function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
7
- return extendStatics(d, b);
8
- };
9
- return function (d, b) {
10
- if (typeof b !== "function" && b !== null)
11
- throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
12
- extendStatics(d, b);
13
- function __() { this.constructor = d; }
14
- d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
- };
16
- })();
17
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
18
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
19
- return new (P || (P = Promise))(function (resolve, reject) {
20
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
21
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
22
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
23
- step((generator = generator.apply(thisArg, _arguments || [])).next());
24
- });
25
- };
26
- var __generator = (this && this.__generator) || function (thisArg, body) {
27
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
28
- return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
29
- function verb(n) { return function (v) { return step([n, v]); }; }
30
- function step(op) {
31
- if (f) throw new TypeError("Generator is already executing.");
32
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
33
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
34
- if (y = 0, t) op = [op[0] & 2, t.value];
35
- switch (op[0]) {
36
- case 0: case 1: t = op; break;
37
- case 4: _.label++; return { value: op[1], done: false };
38
- case 5: _.label++; y = op[1]; op = [0]; continue;
39
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
40
- default:
41
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
42
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
43
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
44
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
45
- if (t[2]) _.ops.pop();
46
- _.trys.pop(); continue;
47
- }
48
- op = body.call(thisArg, _);
49
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
50
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
51
- }
52
- };
53
2
  Object.defineProperty(exports, "__esModule", { value: true });
54
3
  exports.useMethods = exports.ComponentMethods = void 0;
4
+ // Values
55
5
  var react_1 = require("react");
6
+ /**
7
+ * Base class for a class that holds methods intended for use in a function component.
8
+ * These methods will have access to the components state and props via
9
+ * `this.state` and `this.props` respectively.
10
+ *
11
+ * Call the {@link useMethods} hook inside your function component to instantiate the class.
12
+ */
56
13
  var ComponentMethods = /** @class */ (function () {
57
14
  function ComponentMethods() {
58
15
  }
@@ -60,41 +17,48 @@ var ComponentMethods = /** @class */ (function () {
60
17
  }());
61
18
  exports.ComponentMethods = ComponentMethods;
62
19
  ;
63
- var useMethods = function (Methods, props, state) {
64
- // @todo Switch to useRef. Vite HMR seems to sometimes reinitialize useMemo calls after a hot update,
65
- // causing the instance to be unexpectedly recreated in the middle of the components lifecycle.
20
+ /**
21
+ * Returns an instance of the provided class,
22
+ * with the state and props arguments added as instance members.
23
+ *
24
+ * `state` must be an instance of `CleanState` created with {@link useCleanState}.
25
+ */
26
+ var useMethods = function () {
27
+ var args = [];
28
+ for (var _i = 0; _i < arguments.length; _i++) {
29
+ args[_i] = arguments[_i];
30
+ }
31
+ var Methods = args[0], _a = args[1], props = _a === void 0 ? {} : _a, state = args[2];
32
+ // Vite HMR seems to sometimes reinitialize useMemo calls after a hot update,
33
+ // causing the instance to be unexpectedly recreated in the middle of the component's lifecycle.
66
34
  // But useRef and useState values appear to always be preserved whenever this happens.
67
35
  // So those two are the only cross-render-persistence methods we can consider safe.
36
+ // @todo Provide a way for users to reflect updated methods code on the existing instance after HMR.
68
37
  var methods = (0, react_1.useRef)((0, react_1.useMemo)(function () {
69
- // See useLogic implementation for a discussion of this type assertion.
70
38
  return new Methods();
71
39
  }, [])).current;
72
- methods.props = props;
73
- methods.state = state;
40
+ /** A proxy variable to allow typechecking of the assignment to methods.props despite the need for "readonly" error suppression. */
41
+ var _propsProxy_;
42
+ // @ts-expect-error
43
+ methods.props = (_propsProxy_ = props);
44
+ if (state)
45
+ methods.state = state;
74
46
  return methods;
75
47
  };
76
48
  exports.useMethods = useMethods;
77
- testing: {
78
- var a = function () { return __awaiter(void 0, void 0, void 0, function () {
79
- var a, MyMethods, useCleanState, self;
80
- return __generator(this, function (_a) {
81
- switch (_a.label) {
82
- case 0:
83
- a = { b: '' };
84
- MyMethods = /** @class */ (function (_super) {
85
- __extends(MyMethods, _super);
86
- function MyMethods() {
87
- return _super !== null && _super.apply(this, arguments) || this;
88
- }
89
- return MyMethods;
90
- }(ComponentMethods));
91
- ;
92
- return [4 /*yield*/, import('./state.js')];
93
- case 1:
94
- useCleanState = (_a.sent()).useCleanState;
95
- self = (0, exports.useMethods)(MyMethods, {}, useCleanState({}));
96
- return [2 /*return*/];
97
- }
98
- });
99
- }); };
100
- }
49
+ /** /testing: {
50
+ let a = async () => {
51
+ const a: object = {b: ''};
52
+
53
+ type t = keyof typeof a;
54
+
55
+ class MyMethods extends ComponentMethods<WeakEmptyObject, null> {
56
+ // static getInitialState = () => ({});
57
+ };
58
+
59
+ const { useCleanState } = (await import('./state.js'));
60
+
61
+ const self = useMethods(MyMethods, {});
62
+ self.state;
63
+ }
64
+ }/**/
@@ -0,0 +1,17 @@
1
+ import { CleanStateBase } from './class';
2
+ import { TCleanState } from './hook-types';
3
+ export type TCleanStateBase = typeof CleanStateBase;
4
+ export type TCleanStateBaseKeys = keyof TCleanStateBase;
5
+ export type PutState<TState extends object> = {
6
+ [Key in keyof TState]: React.Dispatch<React.SetStateAction<TState[Key]>>;
7
+ };
8
+ export interface ICleanStateConstructor {
9
+ new <TState extends object>(...args: ConstructorParameters<typeof CleanStateBase>): TCleanState<TState>;
10
+ }
11
+ type T = {
12
+ new <TState extends object>(...args: ConstructorParameters<typeof CleanStateBase>): TCleanState<TState>;
13
+ };
14
+ export type ICleanStateClass = T & {
15
+ [Key in TCleanStateBaseKeys]: (typeof CleanStateBase)[Key];
16
+ };
17
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,14 @@
1
+ import { ICleanStateClass, ICleanStateConstructor, PutState } from './class-types';
2
+ export declare class CleanStateBase<TState extends Record<string, any>> {
3
+ readonly reservedKeys: string[];
4
+ readonly valueKeys: string[];
5
+ private _values_;
6
+ private _initialValues_;
7
+ private _setters_;
8
+ constructor(initialState: TState);
9
+ static update: <TState_1 extends object>(this: CleanStateBase<TState_1>) => void;
10
+ get put(): PutState<TState>;
11
+ get initialState(): TState;
12
+ readonly putMany: (newValues: Partial<TState>) => void;
13
+ }
14
+ export declare const CleanState: ICleanStateConstructor & ICleanStateClass;
@@ -11,26 +11,8 @@ var __assign = (this && this.__assign) || function () {
11
11
  return __assign.apply(this, arguments);
12
12
  };
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.useCleanState = exports.useMountState = void 0;
14
+ exports.CleanState = exports.CleanStateBase = void 0;
15
15
  var react_1 = require("react");
16
- /**
17
- * Returns a value that is false before the component has been mounted,
18
- * then true during all subsequent rerenders.
19
- */
20
- var useMountState = function () {
21
- /**
22
- * This must not be a stateful value. It should not be the cause of a rerender.
23
- * It merely provides information about the render count,
24
- * without influencing that count itself.
25
- * So `mounted` should never be set with `useState`.
26
- */
27
- var mounted = (0, react_1.useRef)(false);
28
- (0, react_1.useEffect)(function () {
29
- mounted.current = true;
30
- }, []);
31
- return mounted.current;
32
- };
33
- exports.useMountState = useMountState;
34
16
  var CleanStateBase = /** @class */ (function () {
35
17
  function CleanStateBase(initialState) {
36
18
  var _this = this;
@@ -85,7 +67,7 @@ var CleanStateBase = /** @class */ (function () {
85
67
  });
86
68
  CleanStateBase.update = function update() {
87
69
  var _this = this;
88
- if (!(this instanceof CleanState))
70
+ if (!(this instanceof exports.CleanState))
89
71
  throw new Error('CleanState.update must be called with `this` value set to a CleanState instance. Did you forget to use `.call` or `.apply`? Example: CleanState.update.call(cleanState);');
90
72
  /**
91
73
  * Linters complain about the use of a React hook within a loop because:
@@ -115,39 +97,6 @@ var CleanStateBase = /** @class */ (function () {
115
97
  };
116
98
  return CleanStateBase;
117
99
  }());
100
+ exports.CleanStateBase = CleanStateBase;
118
101
  ;
119
- var CleanState = CleanStateBase;
120
- var useCleanState = function (_initialState) {
121
- var props = [];
122
- for (var _i = 1; _i < arguments.length; _i++) {
123
- props[_i - 1] = arguments[_i];
124
- }
125
- var mounted = (0, exports.useMountState)();
126
- var initialState = typeof _initialState === 'function'
127
- ? (0, react_1.useMemo)(function () { return _initialState.apply(void 0, props); }, [])
128
- : _initialState;
129
- ;
130
- var freshInstance = {};
131
- if (!mounted)
132
- freshInstance = new CleanState(initialState);
133
- if (!freshInstance.put)
134
- throw new Error('useCleanState failed to initialized a state instance.');
135
- var cleanState = (0, react_1.useRef)(freshInstance).current;
136
- CleanState.update.call(cleanState);
137
- return cleanState;
138
- };
139
- exports.useCleanState = useCleanState;
140
- // Should be valid.
141
- // useCleanState((a: number) => ({b: a.toString(), q: 1}), 6);
142
- // useCleanState((a: boolean) => ({b: a.toString()}), true);
143
- // useCleanState((a: number, c?: string) => ({ b: `${a}` }), 6);
144
- // useCleanState((a: number, c?: string) => ({ b: `${a}` }), 6, 'word');
145
- // useCleanState((a: number, c: string) => ({ b: a + c, f: true }), 6, 'text');
146
- // useCleanState({ d: 5000 });
147
- // Should fail.
148
- // useCleanState((a: number) => ({b: a.toString(), q: 1}), 6, false);
149
- // useCleanState((a: boolean) => ({b: a.toString()}));
150
- // useCleanState((a: number, c?: string) => ({ b: `${a}` }), '6');
151
- // useCleanState((a: number, c?: string) => ({ b: `${a}` }));
152
- // useCleanState((a: number, c: string) => ({ b: a + c, f: true }), 6, 7);
153
- // useCleanState({ d: 5000 }, true);
102
+ exports.CleanState = CleanStateBase;