@cleanweb/react 1.0.5 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ declare class MergedState<TState extends object> {
2
+ static refresh<TState extends object>(this: MergedState<TState>): void;
3
+ reservedKeys: string[];
4
+ valueKeys: string[];
5
+ private _initialValues_;
6
+ private _values_;
7
+ private setState;
8
+ private _setters_;
9
+ private useRetrieveState;
10
+ get put(): { [Key in keyof TState]: (value: TState[Key]) => void; };
11
+ get initialState(): TState;
12
+ constructor(initialState: TState);
13
+ putMany: (newValues: Partial<TState>) => void;
14
+ }
15
+ export declare const useMergedState: <TState extends object>(initialState: TState) => MergedState<TState> & TState;
16
+ export {};
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.useMergedState = void 0;
15
+ var react_1 = require("react");
16
+ var MergedState = /** @class */ (function () {
17
+ function MergedState(initialState) {
18
+ var _this = this;
19
+ this._initialValues_ = {};
20
+ this._values_ = {};
21
+ this._setters_ = {};
22
+ this.useRetrieveState = function () {
23
+ var _a;
24
+ _a = (0, react_1.useState)(_this.initialState), _this._values_ = _a[0], _this.setState = _a[1];
25
+ };
26
+ this.putMany = function (newValues) {
27
+ _this.setState(__assign(__assign({}, _this._values_), newValues));
28
+ };
29
+ this.reservedKeys = Object.keys(this);
30
+ this.valueKeys = [];
31
+ this._initialValues_ = __assign({}, initialState);
32
+ this._values_ = __assign({}, initialState);
33
+ Object.keys(initialState).forEach(function (key) {
34
+ if (_this.reservedKeys.includes(key)) {
35
+ throw new Error("The name \"".concat(key, "\" is reserved by CleanState and cannot be used to index state variables. Please use a different key."));
36
+ }
37
+ _this.valueKeys.push(key);
38
+ _this._setters_[key] = function (value) {
39
+ var _a;
40
+ // this._values_[key] = value;
41
+ _this.setState(__assign(__assign({}, _this._values_), (_a = {}, _a[key] = value, _a)));
42
+ };
43
+ var self = _this;
44
+ Object.defineProperty(_this, key, {
45
+ get: function () {
46
+ return self._values_[key];
47
+ },
48
+ set: function (value) {
49
+ self._setters_[key](value);
50
+ },
51
+ enumerable: true,
52
+ });
53
+ });
54
+ }
55
+ MergedState.refresh = function () {
56
+ this.useRetrieveState();
57
+ };
58
+ Object.defineProperty(MergedState.prototype, "put", {
59
+ get: function () {
60
+ return __assign({}, this._setters_);
61
+ },
62
+ enumerable: false,
63
+ configurable: true
64
+ });
65
+ Object.defineProperty(MergedState.prototype, "initialState", {
66
+ get: function () {
67
+ return __assign({}, this._initialValues_);
68
+ },
69
+ enumerable: false,
70
+ configurable: true
71
+ });
72
+ return MergedState;
73
+ }());
74
+ var useMergedState = function (initialState) {
75
+ var cleanState = (0, react_1.useMemo)(function () { return new MergedState(initialState); }, []);
76
+ MergedState.refresh.call(cleanState);
77
+ return cleanState;
78
+ };
79
+ exports.useMergedState = useMergedState;
@@ -1,6 +1,6 @@
1
- import type { CleanState } from './state';
1
+ import type { TCleanState } from './state';
2
2
  export declare class ComponentMethods<TState extends object, TProps extends object> {
3
- state: CleanState<TState>;
3
+ state: TCleanState<TState>;
4
4
  props: TProps;
5
5
  }
6
6
  type ComponentMethodsConstructor = typeof ComponentMethods<any, any>;
@@ -5,18 +5,24 @@ type TUseStateArray<TState extends object> = [
5
5
  type TUseStateResponses<TState extends object> = {
6
6
  [Key in keyof TState]: TUseStateArray<TState>;
7
7
  };
8
- declare class _CleanState_<TState extends object> {
8
+ declare class CleanStateBase<TState extends object> {
9
+ static update: ICleanStateClass['update'];
10
+ reservedKeys: string[];
11
+ valueKeys: string[];
9
12
  private _values_;
10
13
  private _setters_;
11
- put: { [Key in keyof TState]: (value: TState[Key]) => void; };
12
- constructor(stateAndSetters: TUseStateResponses<TState>);
14
+ get put(): { [Key in keyof TState]: (value: TState[Key]) => void; };
15
+ constructor();
13
16
  putMany: (newValues: Partial<TState>) => void;
14
17
  }
15
- type TCleanStateInstance<TState extends object> = TState & _CleanState_<TState>;
16
- declare const _CleanState: new <TState extends object>(...args: ConstructorParameters<typeof _CleanState_>) => TCleanStateInstance<TState>;
17
- export type CleanState<TState extends object> = InstanceType<typeof _CleanState<TState>>;
18
+ type TCleanStateInstance<TState extends object> = TState & CleanStateBase<TState>;
19
+ interface ICleanStateClass {
20
+ update: <TState extends object>(this: CleanStateBase<TState>, stateAndSetters: TUseStateResponses<TState>) => void;
21
+ }
22
+ export type TCleanState<TState extends object> = TCleanStateInstance<TState>;
18
23
  type Func = (...params: any[]) => any;
19
- type UseCleanState = <TStateObjOrFactory extends ((props?: TProps) => object) | object, TProps extends object = object>(_initialState: TStateObjOrFactory, props?: TStateObjOrFactory extends Func ? TProps : undefined) => CleanState<TStateObjOrFactory extends Func ? ReturnType<TStateObjOrFactory> : TStateObjOrFactory>;
24
+ type UseCleanState = <TState extends object, TProps extends object = object>(_initialState: ((props?: TProps) => TState) | TState, // TStateObjOrFactory,
25
+ props?: typeof _initialState extends Func ? TProps : undefined) => TCleanState<TState>;
20
26
  export declare const useCleanState: UseCleanState;
21
27
  /**
22
28
  * Returns a value that is false before the component has been mounted,
@@ -1,27 +1,47 @@
1
1
  "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
2
13
  Object.defineProperty(exports, "__esModule", { value: true });
3
14
  exports.useMountState = exports.useCleanState = void 0;
4
15
  var react_1 = require("react");
5
- var _CleanState_ = /** @class */ (function () {
6
- function _CleanState_(stateAndSetters) {
16
+ var CleanStateBase = /** @class */ (function () {
17
+ function CleanStateBase() {
7
18
  var _this = this;
19
+ this.valueKeys = [];
8
20
  this._values_ = {};
9
21
  this._setters_ = {};
10
- this.put = this._setters_;
11
22
  this.putMany = function (newValues) {
12
23
  Object.entries(newValues).forEach(function (_a) {
13
24
  var key = _a[0], value = _a[1];
14
25
  _this.put[key](value);
15
26
  });
16
27
  };
17
- /** Must be extracted before the loop begins to avoid including keys from the consumers state object. */
18
- var reservedKeys = Object.keys(this);
28
+ this.reservedKeys = Object.keys(this);
29
+ }
30
+ Object.defineProperty(CleanStateBase.prototype, "put", {
31
+ get: function () {
32
+ return __assign({}, this._setters_);
33
+ },
34
+ enumerable: false,
35
+ configurable: true
36
+ });
37
+ CleanStateBase.update = function update(stateAndSetters) {
38
+ var _this = this;
19
39
  Object.entries(stateAndSetters).forEach(function (_a) {
20
40
  var key = _a[0], responseFromUseState = _a[1];
21
- if (reservedKeys.includes(key))
41
+ if (_this.reservedKeys.includes(key))
22
42
  throw new Error("The name \"".concat(key, "\" is reserved by CleanState and cannot be used to index state variables. Please use a different key."));
43
+ _this.valueKeys.push(key);
23
44
  _this._values_[key] = responseFromUseState[0], _this._setters_[key] = responseFromUseState[1];
24
- // this.put[key] = this._setters_[key];
25
45
  var self = _this;
26
46
  Object.defineProperty(_this, key, {
27
47
  get: function () {
@@ -33,11 +53,14 @@ var _CleanState_ = /** @class */ (function () {
33
53
  enumerable: true,
34
54
  });
35
55
  });
36
- }
37
- return _CleanState_;
56
+ // return this;
57
+ };
58
+ return CleanStateBase;
38
59
  }());
39
60
  ;
40
- var _CleanState = _CleanState_;
61
+ var a;
62
+ var CleanState = CleanStateBase;
63
+ var na = new CleanState();
41
64
  /**
42
65
  * Linters complain about the use of a React hook within a loop because:
43
66
  * > By following this rule, you ensure that Hooks are called in the same order each time a component renders.
@@ -50,6 +73,8 @@ var _CleanState = _CleanState_;
50
73
  var retrieveState = react_1.useState;
51
74
  var useCleanState = function (_initialState, props) {
52
75
  var initialState = typeof _initialState === 'function' ? _initialState(props) : _initialState;
76
+ // props?.s
77
+ var cleanState = (0, react_1.useMemo)(function () { return new CleanState(); }, []);
53
78
  var stateKeys = Object.keys(initialState);
54
79
  var initialCount = (0, react_1.useState)(stateKeys.length)[0];
55
80
  if (stateKeys.length !== initialCount) {
@@ -60,10 +85,8 @@ var useCleanState = function (_initialState, props) {
60
85
  var key = stateKeys_1[_i];
61
86
  stateAndSetters[key] = retrieveState(initialState[key]);
62
87
  }
63
- // @todo Refactor to return consistent state instance each render.
64
- // Though useState gives us persistent references for values and setters,
65
- // so keeping the CleanState wrapper persistent may be unnecessary.
66
- return new _CleanState(stateAndSetters);
88
+ CleanState.update.call(cleanState, stateAndSetters);
89
+ return cleanState;
67
90
  };
68
91
  exports.useCleanState = useCleanState;
69
92
  /**
@@ -5,6 +5,12 @@ type Obj = Record<string, any>;
5
5
  type IComponentConstructor = ComponentInstanceConstructor<any, any, any> & typeof ClassComponent<any, any, any>;
6
6
  export declare class ClassComponent<TState extends Obj, TProps extends Obj, THooks extends Obj> extends ComponentInstance<TState, TProps, THooks> {
7
7
  Render: FunctionComponent<TProps>;
8
+ /**
9
+ * Use this to let React know whenever you would like all of your instance's state to be reset.
10
+ * When the value is changed, React will reset all state variables to their initial value the next time your component re-renders.
11
+ * @see https://react.dev/learn/you-might-not-need-an-effect#resetting-all-state-when-a-prop-changes
12
+ */
13
+ instanceId?: string;
8
14
  static FC: <IComponentType extends IComponentConstructor>(this: IComponentType, _Component?: IComponentType) => (props: InstanceType<IComponentType>["props"]) => JSX.Element;
9
15
  }
10
16
  export {};
@@ -43,10 +43,10 @@ var ClassComponent = /** @class */ (function (_super) {
43
43
  if (!Component.getInitialState || !isClassComponentType)
44
44
  throw new Error('Attempted to initialize ClassComponent with invalid Class type. Either pass a class that extends ClassComponent to FC (e.g `export FC(MyComponent);`), or ensure it is called as a method on a ClassComponent constructor type (e.g `export MyComponent.FC()`).');
45
45
  var Wrapper = function (props) {
46
- var Render = (0, instance_1.useInstance)(Component, props).Render;
46
+ var _a = (0, instance_1.useInstance)(Component, props), Render = _a.Render, instanceId = _a.instanceId;
47
47
  // Add calling component name to Render function name in stack traces.
48
48
  (0, react_1.useMemo)(function () { return setFunctionName(Render, "".concat(Component.name, ".Render")); }, []);
49
- return (0, jsx_runtime_1.jsx)(Render, {});
49
+ return (0, jsx_runtime_1.jsx)(Render, {}, instanceId);
50
50
  };
51
51
  // Include calling component name in wrapper function name on stack traces.
52
52
  var wrapperName = "ClassComponent".concat(Wrapper.name, " > ").concat(Component.name);
@@ -1,13 +1,51 @@
1
1
  import type { ComponentLogicConstructor } from './logic';
2
2
  import { ComponentLogic } from './logic';
3
3
  type Obj = Record<string, any>;
4
- type AsyncAllowedEffectCallback = () => IVoidFunction | Promise<IVoidFunction>;
4
+ type AsyncAllowedEffectCallback = () => Awaitable<IVoidFunction>;
5
5
  export declare const noOp: () => void;
6
6
  export declare class ComponentInstance<TState extends Obj = {}, TProps extends Obj = {}, THooks extends Obj = {}> extends ComponentLogic<TState, TProps, THooks> {
7
+ /**
8
+ * Runs only _before_ first render, i.e before the component instance is mounted.
9
+ * Useful for logic that is involved in determining what to render.
10
+ *
11
+ * Updating local state from in here will abort the render cycle early, before changes are committed to the DOM,
12
+ * and prompt React to immediately rerender the component with the updated state value(s).
13
+ *
14
+ * Ignored on subsequent rerenders.
15
+ */
7
16
  beforeMount: IVoidFunction;
17
+ /**
18
+ * Runs only **_after_** first render, i.e after the component instance is mounted.
19
+ *
20
+ * Should usually only be used for logic that does not directly take part in determining what to render, like
21
+ * synchronize your component with some external system.
22
+ *
23
+ * Ignored on subsequent rerenders.
24
+ *
25
+ * Returns a cleanup function.
26
+ */
8
27
  onMount: AsyncAllowedEffectCallback;
28
+ /**
29
+ * Runs _before_ every render cycle, including the first.
30
+ * Useful for logic that is involved in determining what to render.
31
+ *
32
+ * Updating local state from in here will abort the render cycle early, before changes are committed to the DOM,
33
+ * and prompt React to immediately rerender the component with the updated state value(s).
34
+ */
9
35
  beforeRender: IVoidFunction;
36
+ /**
37
+ * Runs **_after_** every render cycle, including the first.
38
+ *
39
+ * Should usually only be used for logic that does not directly take part in determining what to render, like
40
+ * synchronize your component with some external system.
41
+ *
42
+ * Returns a cleanup function.
43
+ */
10
44
  onRender: AsyncAllowedEffectCallback;
45
+ /**
46
+ * Runs when the component is unmounted.
47
+ * It is called _after_ the cleanup function returned by onMount.
48
+ */
11
49
  cleanUp: IVoidFunction;
12
50
  }
13
51
  type ComponentClassBaseType<TState extends Obj = {}, TProps extends Obj = {}, THooks extends Obj = {}> = ComponentLogicConstructor<TState, TProps, THooks> & Constructor<ComponentInstance<TState, TProps, THooks>>;
@@ -25,10 +25,48 @@ var ComponentInstance = /** @class */ (function (_super) {
25
25
  __extends(ComponentInstance, _super);
26
26
  function ComponentInstance() {
27
27
  var _this = _super !== null && _super.apply(this, arguments) || this;
28
+ /**
29
+ * Runs only _before_ first render, i.e before the component instance is mounted.
30
+ * Useful for logic that is involved in determining what to render.
31
+ *
32
+ * Updating local state from in here will abort the render cycle early, before changes are committed to the DOM,
33
+ * and prompt React to immediately rerender the component with the updated state value(s).
34
+ *
35
+ * Ignored on subsequent rerenders.
36
+ */
28
37
  _this.beforeMount = function () { };
38
+ /**
39
+ * Runs only **_after_** first render, i.e after the component instance is mounted.
40
+ *
41
+ * Should usually only be used for logic that does not directly take part in determining what to render, like
42
+ * synchronize your component with some external system.
43
+ *
44
+ * Ignored on subsequent rerenders.
45
+ *
46
+ * Returns a cleanup function.
47
+ */
29
48
  _this.onMount = function () { return exports.noOp; };
49
+ /**
50
+ * Runs _before_ every render cycle, including the first.
51
+ * Useful for logic that is involved in determining what to render.
52
+ *
53
+ * Updating local state from in here will abort the render cycle early, before changes are committed to the DOM,
54
+ * and prompt React to immediately rerender the component with the updated state value(s).
55
+ */
30
56
  _this.beforeRender = function () { };
57
+ /**
58
+ * Runs **_after_** every render cycle, including the first.
59
+ *
60
+ * Should usually only be used for logic that does not directly take part in determining what to render, like
61
+ * synchronize your component with some external system.
62
+ *
63
+ * Returns a cleanup function.
64
+ */
31
65
  _this.onRender = function () { return exports.noOp; };
66
+ /**
67
+ * Runs when the component is unmounted.
68
+ * It is called _after_ the cleanup function returned by onMount.
69
+ */
32
70
  _this.cleanUp = function () { };
33
71
  return _this;
34
72
  }
@@ -48,6 +86,7 @@ var useMountCallbacks = function (instance) {
48
86
  var doCleanUp = function (runMountCleaners) {
49
87
  var _a;
50
88
  runMountCleaners === null || runMountCleaners === void 0 ? void 0 : runMountCleaners();
89
+ // onDismount? willUnmount?
51
90
  (_a = instance.cleanUp) === null || _a === void 0 ? void 0 : _a.call(instance);
52
91
  };
53
92
  if (typeof mountHandlerCleanUp === 'function') {
@@ -62,7 +101,7 @@ var useMountCallbacks = function (instance) {
62
101
  exports.useMountCallbacks = useMountCallbacks;
63
102
  var useInstance = function (Component, props) {
64
103
  var _a;
65
- // useCustomHooks.
104
+ // useHooks.
66
105
  var instance = (0, logic_1.useLogic)(Component, props);
67
106
  // beforeMount, onMount, cleanUp.
68
107
  (0, exports.useMountCallbacks)(instance);
@@ -1,9 +1,9 @@
1
- import type { CleanState } from '../base/state';
1
+ import type { TCleanState } from '../base/state';
2
2
  export declare class ComponentLogic<TState extends object, TProps extends object, THooks extends object> {
3
- state: CleanState<TState>;
3
+ state: TCleanState<TState>;
4
4
  props: TProps;
5
5
  hooks: THooks;
6
- useCustomHooks?: () => THooks;
6
+ useHooks?: () => THooks;
7
7
  }
8
8
  export interface ComponentLogicConstructor<TState extends object, TProps extends object, THooks extends object> extends Constructor<ComponentLogic<TState, TProps, THooks>> {
9
9
  getInitialState: (props?: TProps) => TState;
@@ -24,7 +24,7 @@ var useLogic = function (Methods, props) {
24
24
  }, []);
25
25
  methods.state = state;
26
26
  methods.props = props;
27
- methods.hooks = ((_a = methods.useCustomHooks) === null || _a === void 0 ? void 0 : _a.call(methods)) || {};
27
+ methods.hooks = ((_a = methods.useHooks) === null || _a === void 0 ? void 0 : _a.call(methods)) || {};
28
28
  return methods;
29
29
  };
30
30
  exports.useLogic = useLogic;
@@ -8,6 +8,8 @@ type Optional<
8
8
  : BaseType | undefined
9
9
  )
10
10
 
11
+ type Awaitable<Type> = Type | Promise<Type>;
12
+
11
13
  type Constructor<
12
14
  TInstance extends any = any,
13
15
  TParams extends any[] = never[]
package/build/index.js CHANGED
@@ -15,3 +15,15 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./classy"), exports);
18
+ // PS: Document component inheritance pattern with lifecycle callback arrays and namespaces.
19
+ // Due to react's remounting behaviour, components must externally track when some logic has run, if it really really must only ever run once per mounted instance. Tricky to get right for components that may have multiple instance rendered simultaneously at different parts of a page.
20
+ // Note: There is an alternative clean-state implementation that uses a single useState call and passes the clean state instance.
21
+ // Then when a state setter is used, it mutates the cleanstate object, then calls setState on the updated cleanstate.
22
+ // But setState might ignore calls with the same object ref as the existing state value, so perhaps create a new cleanstate
23
+ // instance instead, spreading existing values with the changed values, and call setState with that.
24
+ // It could be more performant as it would remove the need for looping over Object.keys in useCleanState.
25
+ // Investigate this for a potential minor version update.
26
+ // useCleanState => useState
27
+ // useMethods => useCallback
28
+ // useLogic => useCallback + all other hook calls.
29
+ // useInstance => useLogic + lifecycle methods.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleanweb/react",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "A suite of helpers for writing cleaner React function components.",
5
5
  "engines": {
6
6
  "node": ">=18"