@cleanweb/react 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +0 -0
- package/build/README.md +0 -0
- package/build/class.d.ts +10 -0
- package/build/class.jsx +54 -0
- package/build/globals.d.ts +52 -0
- package/build/instance.d.ts +18 -0
- package/build/instance.js +81 -0
- package/build/logic.d.ts +13 -0
- package/build/logic.js +26 -0
- package/build/methods.d.ts +9 -0
- package/build/methods.js +29 -0
- package/build/package.json +43 -0
- package/build/state.d.ts +26 -0
- package/build/state.js +81 -0
- package/build/tsconfig.json +43 -0
- package/package.json +43 -0
package/README.md
ADDED
File without changes
|
package/build/README.md
ADDED
File without changes
|
package/build/class.d.ts
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
import type { ComponentInstanceConstructor } from '@/instance';
|
2
|
+
import { FunctionComponent } from "react";
|
3
|
+
import { ComponentInstance } from '@/instance';
|
4
|
+
type Obj = Record<string, any>;
|
5
|
+
type IComponentConstructor = ComponentInstanceConstructor<any, any, any> & typeof ClassComponent<any, any, any>;
|
6
|
+
export declare class ClassComponent<TState extends Obj, TProps extends Obj, THooks extends Obj> extends ComponentInstance<TState, TProps, THooks> {
|
7
|
+
Render: FunctionComponent<TProps>;
|
8
|
+
static FC: <IComponentType extends IComponentConstructor>(this: IComponentType, _Component?: IComponentType) => (props: InstanceType<IComponentType>["props"]) => import("react").JSX.Element;
|
9
|
+
}
|
10
|
+
export {};
|
package/build/class.jsx
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
var __extends = (this && this.__extends) || (function () {
|
2
|
+
var extendStatics = function (d, b) {
|
3
|
+
extendStatics = Object.setPrototypeOf ||
|
4
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
5
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
6
|
+
return extendStatics(d, b);
|
7
|
+
};
|
8
|
+
return function (d, b) {
|
9
|
+
if (typeof b !== "function" && b !== null)
|
10
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
11
|
+
extendStatics(d, b);
|
12
|
+
function __() { this.constructor = d; }
|
13
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
14
|
+
};
|
15
|
+
})();
|
16
|
+
import { useMemo } from "react";
|
17
|
+
import { ComponentInstance, useInstance } from '@/instance';
|
18
|
+
/** Provide more useful stack traces for otherwise non-specific function names. */
|
19
|
+
var setFunctionName = function (func, newName) {
|
20
|
+
try {
|
21
|
+
// Must use try block, as `name` is not configurable on older browsers, and may yield a TypeError.
|
22
|
+
Object.defineProperty(func, 'name', {
|
23
|
+
writable: true,
|
24
|
+
value: newName,
|
25
|
+
});
|
26
|
+
}
|
27
|
+
catch (error) {
|
28
|
+
console.warn(error);
|
29
|
+
}
|
30
|
+
};
|
31
|
+
var ClassComponent = /** @class */ (function (_super) {
|
32
|
+
__extends(ClassComponent, _super);
|
33
|
+
function ClassComponent() {
|
34
|
+
return _super !== null && _super.apply(this, arguments) || this;
|
35
|
+
}
|
36
|
+
ClassComponent.FC = function FC(_Component) {
|
37
|
+
var Component = _Component || this;
|
38
|
+
var isClassComponentType = Component.prototype instanceof ClassComponent;
|
39
|
+
if (!Component.getInitialState || !isClassComponentType)
|
40
|
+
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()`).');
|
41
|
+
var Wrapper = function (props) {
|
42
|
+
var Render = useInstance(Component, props).Render;
|
43
|
+
// Add calling component name to Render function name in stack traces.
|
44
|
+
useMemo(function () { return setFunctionName(Render, "".concat(Component.name, ".Render")); }, []);
|
45
|
+
return <Render />;
|
46
|
+
};
|
47
|
+
// Include calling component name in wrapper function name on stack traces.
|
48
|
+
var wrapperName = "ClassComponent".concat(Wrapper.name, " > ").concat(Component.name);
|
49
|
+
setFunctionName(Wrapper, wrapperName);
|
50
|
+
return Wrapper;
|
51
|
+
};
|
52
|
+
return ClassComponent;
|
53
|
+
}(ComponentInstance));
|
54
|
+
export { ClassComponent };
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
type Optional<
|
3
|
+
BaseType,
|
4
|
+
AllowNull extends boolean = true
|
5
|
+
> = (
|
6
|
+
AllowNull extends true
|
7
|
+
? BaseType | undefined | null
|
8
|
+
: BaseType | undefined
|
9
|
+
)
|
10
|
+
|
11
|
+
type Constructor<
|
12
|
+
TInstance extends any = any,
|
13
|
+
TParams extends any[] = never[]
|
14
|
+
> = new (...args: TParams) => TInstance
|
15
|
+
|
16
|
+
|
17
|
+
/**
|
18
|
+
* @example
|
19
|
+
* ```js
|
20
|
+
* const getNumber: AsyncFunction<number> = async () => {
|
21
|
+
* await oneTickDelay();
|
22
|
+
* return 5;
|
23
|
+
* }
|
24
|
+
* ```
|
25
|
+
*/
|
26
|
+
declare type AsyncFunction<
|
27
|
+
TReturnValue extends any = void,
|
28
|
+
Params extends any[] = never[]
|
29
|
+
> = (...params: Params) => Promise<TReturnValue>
|
30
|
+
|
31
|
+
/**
|
32
|
+
* A function that takes no arguments and returns nothing.
|
33
|
+
* Pass a type argument to set whether `async` and/or `sync` functions are allowed.
|
34
|
+
*/
|
35
|
+
declare interface IVoidFunction<AsyncType extends 'async' | 'sync' | 'both' = 'both'> {
|
36
|
+
(): AsyncType extends 'async' ? Promise<void>
|
37
|
+
: AsyncType extends 'sync' ? void
|
38
|
+
: Promise<void> | void
|
39
|
+
}
|
40
|
+
|
41
|
+
declare interface Window {
|
42
|
+
}
|
43
|
+
|
44
|
+
declare namespace JSX {
|
45
|
+
interface IntrinsicElements {
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
declare namespace NodeJS {
|
50
|
+
interface ProcessEnv {
|
51
|
+
}
|
52
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { ComponentLogic, ComponentLogicConstructor } from "./logic";
|
2
|
+
type Obj = Record<string, any>;
|
3
|
+
type AsyncAllowedEffectCallback = () => IVoidFunction | Promise<IVoidFunction>;
|
4
|
+
export declare const noOp: () => void;
|
5
|
+
export declare class ComponentInstance<TState extends Obj = {}, TProps extends Obj = {}, THooks extends Obj = {}> extends ComponentLogic<TState, TProps, THooks> {
|
6
|
+
beforeMount: IVoidFunction;
|
7
|
+
onMount: AsyncAllowedEffectCallback;
|
8
|
+
beforeRender: IVoidFunction;
|
9
|
+
onRender: AsyncAllowedEffectCallback;
|
10
|
+
cleanUp: IVoidFunction;
|
11
|
+
}
|
12
|
+
type ComponentClassBaseType<TState extends Obj = {}, TProps extends Obj = {}, THooks extends Obj = {}> = ComponentLogicConstructor<TState, TProps, THooks> & Constructor<ComponentInstance<TState, TProps, THooks>>;
|
13
|
+
export interface ComponentInstanceConstructor<TState extends Obj = {}, TProps extends Obj = {}, THooks extends Obj = {}> extends ComponentClassBaseType<TState, TProps, THooks> {
|
14
|
+
}
|
15
|
+
type UseInstance = <TClass extends ComponentInstanceConstructor>(Class: TClass, props: InstanceType<TClass>['props']) => InstanceType<TClass>;
|
16
|
+
export declare const useMountCallbacks: <TInstance extends ComponentInstance<any, any, any>>(instance: TInstance) => void;
|
17
|
+
export declare const useInstance: UseInstance;
|
18
|
+
export {};
|
@@ -0,0 +1,81 @@
|
|
1
|
+
var __extends = (this && this.__extends) || (function () {
|
2
|
+
var extendStatics = function (d, b) {
|
3
|
+
extendStatics = Object.setPrototypeOf ||
|
4
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
5
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
6
|
+
return extendStatics(d, b);
|
7
|
+
};
|
8
|
+
return function (d, b) {
|
9
|
+
if (typeof b !== "function" && b !== null)
|
10
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
11
|
+
extendStatics(d, b);
|
12
|
+
function __() { this.constructor = d; }
|
13
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
14
|
+
};
|
15
|
+
})();
|
16
|
+
import { useEffect } from "react";
|
17
|
+
import { useMountState } from "./state";
|
18
|
+
import { ComponentLogic, useLogic } from "./logic";
|
19
|
+
export var noOp = function () { };
|
20
|
+
var ComponentInstance = /** @class */ (function (_super) {
|
21
|
+
__extends(ComponentInstance, _super);
|
22
|
+
function ComponentInstance() {
|
23
|
+
var _this = _super !== null && _super.apply(this, arguments) || this;
|
24
|
+
_this.beforeMount = function () { };
|
25
|
+
_this.onMount = function () { return noOp; };
|
26
|
+
_this.beforeRender = function () { };
|
27
|
+
_this.onRender = function () { return noOp; };
|
28
|
+
_this.cleanUp = function () { };
|
29
|
+
return _this;
|
30
|
+
}
|
31
|
+
return ComponentInstance;
|
32
|
+
}(ComponentLogic));
|
33
|
+
export { ComponentInstance };
|
34
|
+
;
|
35
|
+
export var useMountCallbacks = function (instance) {
|
36
|
+
var _a;
|
37
|
+
var mounted = useMountState();
|
38
|
+
if (!mounted)
|
39
|
+
(_a = instance.beforeMount) === null || _a === void 0 ? void 0 : _a.call(instance);
|
40
|
+
useEffect(function () {
|
41
|
+
var _a;
|
42
|
+
var mountHandlerCleanUp = (_a = instance.onMount) === null || _a === void 0 ? void 0 : _a.call(instance);
|
43
|
+
return function () {
|
44
|
+
var doCleanUp = function (runMountCleaners) {
|
45
|
+
var _a;
|
46
|
+
runMountCleaners === null || runMountCleaners === void 0 ? void 0 : runMountCleaners();
|
47
|
+
(_a = instance.cleanUp) === null || _a === void 0 ? void 0 : _a.call(instance);
|
48
|
+
};
|
49
|
+
if (typeof mountHandlerCleanUp === 'function') {
|
50
|
+
doCleanUp(mountHandlerCleanUp);
|
51
|
+
}
|
52
|
+
else {
|
53
|
+
mountHandlerCleanUp === null || mountHandlerCleanUp === void 0 ? void 0 : mountHandlerCleanUp.then(doCleanUp);
|
54
|
+
}
|
55
|
+
};
|
56
|
+
}, []);
|
57
|
+
};
|
58
|
+
export var useInstance = function (Component, props) {
|
59
|
+
var _a;
|
60
|
+
// useCustomHooks.
|
61
|
+
var instance = useLogic(Component, props);
|
62
|
+
// beforeMount, onMount, cleanUp.
|
63
|
+
useMountCallbacks(instance);
|
64
|
+
(_a = instance.beforeRender) === null || _a === void 0 ? void 0 : _a.call(instance);
|
65
|
+
useEffect(function () {
|
66
|
+
var _a;
|
67
|
+
var cleanupAfterRerender = (_a = instance.onRender) === null || _a === void 0 ? void 0 : _a.call(instance);
|
68
|
+
return function () {
|
69
|
+
var doCleanUp = function (runRenderCleanup) {
|
70
|
+
runRenderCleanup === null || runRenderCleanup === void 0 ? void 0 : runRenderCleanup();
|
71
|
+
};
|
72
|
+
if (typeof cleanupAfterRerender === 'function') {
|
73
|
+
doCleanUp(cleanupAfterRerender);
|
74
|
+
}
|
75
|
+
else {
|
76
|
+
cleanupAfterRerender === null || cleanupAfterRerender === void 0 ? void 0 : cleanupAfterRerender.then(doCleanUp);
|
77
|
+
}
|
78
|
+
};
|
79
|
+
});
|
80
|
+
return instance;
|
81
|
+
};
|
package/build/logic.d.ts
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
import type { CleanState } from "./state";
|
2
|
+
export declare class ComponentLogic<TState extends object, TProps extends object, THooks extends object> {
|
3
|
+
state: CleanState<TState>;
|
4
|
+
props: TProps;
|
5
|
+
hooks: THooks;
|
6
|
+
useCustomHooks?: () => THooks;
|
7
|
+
}
|
8
|
+
export interface ComponentLogicConstructor<TState extends object, TProps extends object, THooks extends object> extends Constructor<ComponentLogic<TState, TProps, THooks>> {
|
9
|
+
getInitialState: (props?: TProps) => TState;
|
10
|
+
}
|
11
|
+
type UseLogic = <TState extends object, LogicClass extends ComponentLogicConstructor<TState, any, any>>(Methods: LogicClass, props: InstanceType<LogicClass>['props']) => InstanceType<LogicClass>;
|
12
|
+
export declare const useLogic: UseLogic;
|
13
|
+
export {};
|
package/build/logic.js
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
import { useMemo } from "react";
|
2
|
+
import { useCleanState } from "./state";
|
3
|
+
var ComponentLogic = /** @class */ (function () {
|
4
|
+
function ComponentLogic() {
|
5
|
+
}
|
6
|
+
return ComponentLogic;
|
7
|
+
}());
|
8
|
+
export { ComponentLogic };
|
9
|
+
;
|
10
|
+
export var useLogic = function (Methods, props) {
|
11
|
+
var _a;
|
12
|
+
var state = useCleanState(Methods.getInitialState, props);
|
13
|
+
// There's apparently a bug? with Typescript that pegs the return type of "new Methods()" to "ComponentLogic<{}, {}, {}>",
|
14
|
+
// completely ignoring the type specified for Methods in the function's type definition.
|
15
|
+
// `new Methods()` should return whatever the InstanceType of TClass is, as that is the type explicitly specified for Methods.
|
16
|
+
// Ignoring the specified type to gin up something else and then complain about it is quite weird.
|
17
|
+
// Regardless, even when `extends ComponentLogicConstructor<TState, TProps, THooks>` is specified using generics instead of a set type,
|
18
|
+
// the issue persists. Which is absurd since this should ensure that InstanceType<Class> should exactly match ComponentLogic<TState, TProps, THooks>
|
19
|
+
var methods = useMemo(function () {
|
20
|
+
return new Methods();
|
21
|
+
}, []);
|
22
|
+
methods.state = state;
|
23
|
+
methods.props = props;
|
24
|
+
methods.hooks = ((_a = methods.useCustomHooks) === null || _a === void 0 ? void 0 : _a.call(methods)) || {};
|
25
|
+
return methods;
|
26
|
+
};
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import type { CleanState } from "./state";
|
2
|
+
export declare class ComponentMethods<TState extends object, TProps extends object> {
|
3
|
+
state: CleanState<TState>;
|
4
|
+
props: TProps;
|
5
|
+
}
|
6
|
+
type ComponentMethodsConstructor = typeof ComponentMethods<any, any>;
|
7
|
+
type UseMethods = <MethodsClass extends ComponentMethodsConstructor>(Methods: MethodsClass, state: InstanceType<MethodsClass>['state'], props: InstanceType<MethodsClass>['props']) => InstanceType<MethodsClass>;
|
8
|
+
export declare const useMethods: UseMethods;
|
9
|
+
export {};
|
package/build/methods.js
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
2
|
+
__assign = Object.assign || function(t) {
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
4
|
+
s = arguments[i];
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
6
|
+
t[p] = s[p];
|
7
|
+
}
|
8
|
+
return t;
|
9
|
+
};
|
10
|
+
return __assign.apply(this, arguments);
|
11
|
+
};
|
12
|
+
import { useMemo } from "react";
|
13
|
+
var ComponentMethods = /** @class */ (function () {
|
14
|
+
function ComponentMethods() {
|
15
|
+
}
|
16
|
+
return ComponentMethods;
|
17
|
+
}());
|
18
|
+
export { ComponentMethods };
|
19
|
+
;
|
20
|
+
export var useMethods = function (Methods, state, props) {
|
21
|
+
var methods = useMemo(function () {
|
22
|
+
// See useLogic implementation for a discussion of this type assertion.
|
23
|
+
return new Methods();
|
24
|
+
}, []);
|
25
|
+
methods.state = state;
|
26
|
+
methods.props = props;
|
27
|
+
// Return a gate object to "passthrough" all methods but filter out properties that should be private.
|
28
|
+
return __assign(__assign({}, methods), { props: undefined, state: undefined });
|
29
|
+
};
|
@@ -0,0 +1,43 @@
|
|
1
|
+
{
|
2
|
+
"name": "@cleanweb/react",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "A suite of helpers for writing cleaner React function components.",
|
5
|
+
"engines": {
|
6
|
+
"node": ">=18"
|
7
|
+
},
|
8
|
+
"files": [
|
9
|
+
"build"
|
10
|
+
],
|
11
|
+
"scripts": {
|
12
|
+
"build": "rimraf ./build && tsc && copyfiles README.md package.json .npmrc globals.d.ts tsconfig.json build",
|
13
|
+
"prepublishOnly": "npm run build",
|
14
|
+
"postpublish": "cd ./mirror-pkg && npm publish && cd ..",
|
15
|
+
"test": "echo \"No tests ATM\""
|
16
|
+
},
|
17
|
+
"keywords": [
|
18
|
+
"react",
|
19
|
+
"clean-react",
|
20
|
+
"clean react",
|
21
|
+
"function components",
|
22
|
+
"hooks",
|
23
|
+
"react hooks",
|
24
|
+
"state",
|
25
|
+
"clean state",
|
26
|
+
"group state"
|
27
|
+
],
|
28
|
+
"author": {
|
29
|
+
"name": "Feranmi Akinlade",
|
30
|
+
"url": "https://feranmi.dev"
|
31
|
+
},
|
32
|
+
"license": "MIT",
|
33
|
+
"devDependencies": {
|
34
|
+
"@types/node": "20.14.10",
|
35
|
+
"@types/react": "^16",
|
36
|
+
"copyfiles": "^2.4.1",
|
37
|
+
"rimraf": "^6.0.1",
|
38
|
+
"typescript": "^5.6.2"
|
39
|
+
},
|
40
|
+
"peerDependencies": {
|
41
|
+
"react": ">=16"
|
42
|
+
}
|
43
|
+
}
|
package/build/state.d.ts
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
type TUseStateArray<TState extends object> = [
|
2
|
+
val: TState[keyof TState],
|
3
|
+
setter: (val: TState[keyof TState]) => void
|
4
|
+
];
|
5
|
+
type TUseStateResponses<TState extends object> = {
|
6
|
+
[Key in keyof TState]: TUseStateArray<TState>;
|
7
|
+
};
|
8
|
+
declare class _CleanState_<TState extends object> {
|
9
|
+
private _values_;
|
10
|
+
private _setters_;
|
11
|
+
put: { [Key in keyof TState]: (value: TState[Key]) => void; };
|
12
|
+
constructor(stateAndSetters: TUseStateResponses<TState>);
|
13
|
+
putMany: (newValues: Partial<TState>) => void;
|
14
|
+
}
|
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 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>;
|
20
|
+
export declare const useCleanState: UseCleanState;
|
21
|
+
/**
|
22
|
+
* Returns a value that is false before the component has been mounted,
|
23
|
+
* then true during all subsequent rerenders.
|
24
|
+
*/
|
25
|
+
export declare const useMountState: () => boolean;
|
26
|
+
export {};
|
package/build/state.js
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
2
|
+
var _CleanState_ = /** @class */ (function () {
|
3
|
+
function _CleanState_(stateAndSetters) {
|
4
|
+
var _this = this;
|
5
|
+
this._values_ = {};
|
6
|
+
this._setters_ = {};
|
7
|
+
this.put = this._setters_;
|
8
|
+
this.putMany = function (newValues) {
|
9
|
+
Object.entries(newValues).forEach(function (_a) {
|
10
|
+
var key = _a[0], value = _a[1];
|
11
|
+
_this.put[key](value);
|
12
|
+
});
|
13
|
+
};
|
14
|
+
/** Must be extracted before the loop begins to avoid including keys from the consumers state object. */
|
15
|
+
var reservedKeys = Object.keys(this);
|
16
|
+
Object.entries(stateAndSetters).forEach(function (_a) {
|
17
|
+
var key = _a[0], responseFromUseState = _a[1];
|
18
|
+
if (reservedKeys.includes(key))
|
19
|
+
throw new Error("The name \"".concat(key, "\" is reserved by CleanState and cannot be used to index state variables. Please use a different key."));
|
20
|
+
_this._values_[key] = responseFromUseState[0], _this._setters_[key] = responseFromUseState[1];
|
21
|
+
_this.put[key] = _this._setters_[key];
|
22
|
+
var self = _this;
|
23
|
+
Object.defineProperty(_this, key, {
|
24
|
+
get: function () {
|
25
|
+
return self._values_[key];
|
26
|
+
},
|
27
|
+
set: function (value) {
|
28
|
+
self._setters_[key](value);
|
29
|
+
},
|
30
|
+
enumerable: true,
|
31
|
+
});
|
32
|
+
});
|
33
|
+
}
|
34
|
+
return _CleanState_;
|
35
|
+
}());
|
36
|
+
;
|
37
|
+
var _CleanState = _CleanState_;
|
38
|
+
/**
|
39
|
+
* Linters complain about the use of a React hook within a loop because:
|
40
|
+
* > By following this rule, you ensure that Hooks are called in the same order each time a component renders.
|
41
|
+
* > That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.
|
42
|
+
* To resolve this, we're calling `useState` via an alias `retrieveState`.
|
43
|
+
* Bypassing this rule is safe here because `useCleanState` is a special case,
|
44
|
+
* and it guarantees that the same useState calls will be made on every render in the exact same order.
|
45
|
+
* Therefore, it is safe to silence the linters, and required for this implementation to work smoothly.
|
46
|
+
*/
|
47
|
+
var retrieveState = useState;
|
48
|
+
export var useCleanState = function (_initialState, props) {
|
49
|
+
var initialState = typeof _initialState === 'function' ? _initialState(props) : _initialState;
|
50
|
+
var stateKeys = Object.keys(initialState);
|
51
|
+
var initialCount = useState(stateKeys.length)[0];
|
52
|
+
if (stateKeys.length !== initialCount) {
|
53
|
+
throw new Error('The keys in your state object must be consistent throughout your components lifetime. Look up "rules of hooks" for more context.');
|
54
|
+
}
|
55
|
+
var stateAndSetters = {};
|
56
|
+
for (var _i = 0, stateKeys_1 = stateKeys; _i < stateKeys_1.length; _i++) {
|
57
|
+
var key = stateKeys_1[_i];
|
58
|
+
stateAndSetters[key] = retrieveState(initialState[key]);
|
59
|
+
}
|
60
|
+
// @todo Refactor to return consistent state instance each render.
|
61
|
+
// Though useState gives us persistent references for values and setters,
|
62
|
+
// so keeping the CleanState wrapper persistent may be unnecessary.
|
63
|
+
return new _CleanState(stateAndSetters);
|
64
|
+
};
|
65
|
+
/**
|
66
|
+
* Returns a value that is false before the component has been mounted,
|
67
|
+
* then true during all subsequent rerenders.
|
68
|
+
*/
|
69
|
+
export var useMountState = function () {
|
70
|
+
/**
|
71
|
+
* This must not be a stateful value. It should not be the cause of a rerender.
|
72
|
+
* It merely provides information about the render count,
|
73
|
+
* without influencing that count itself.
|
74
|
+
* So `mounted` should never be set with `useState`.
|
75
|
+
*/
|
76
|
+
var mounted = useRef(false);
|
77
|
+
useEffect(function () {
|
78
|
+
mounted.current = true;
|
79
|
+
}, []);
|
80
|
+
return mounted.current;
|
81
|
+
};
|
@@ -0,0 +1,43 @@
|
|
1
|
+
{
|
2
|
+
"compilerOptions": {
|
3
|
+
"baseUrl": ".",
|
4
|
+
"rootDir": ".",
|
5
|
+
"outDir": "build",
|
6
|
+
"paths": {
|
7
|
+
"@/*": [
|
8
|
+
"./*"
|
9
|
+
]
|
10
|
+
},
|
11
|
+
"target": "es5",
|
12
|
+
"lib": [
|
13
|
+
"dom",
|
14
|
+
"dom.iterable",
|
15
|
+
"esnext"
|
16
|
+
],
|
17
|
+
"allowJs": true,
|
18
|
+
"checkJs": true,
|
19
|
+
"declaration": true,
|
20
|
+
"noEmit": false,
|
21
|
+
"skipLibCheck": true,
|
22
|
+
"strict": false,
|
23
|
+
"forceConsistentCasingInFileNames": true,
|
24
|
+
"incremental": false,
|
25
|
+
"esModuleInterop": true,
|
26
|
+
"module": "esnext",
|
27
|
+
"moduleResolution": "node",
|
28
|
+
"resolveJsonModule": true,
|
29
|
+
"isolatedModules": true,
|
30
|
+
"jsx": "preserve",
|
31
|
+
"strictNullChecks": true
|
32
|
+
},
|
33
|
+
"include": [
|
34
|
+
"next-env.d.ts",
|
35
|
+
"**/*.ts",
|
36
|
+
"**/*.tsx"
|
37
|
+
],
|
38
|
+
"exclude": [
|
39
|
+
"node_modules/**/**.*",
|
40
|
+
"build/**/**.*",
|
41
|
+
"mirror-pkg/**/**.*"
|
42
|
+
]
|
43
|
+
}
|
package/package.json
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
{
|
2
|
+
"name": "@cleanweb/react",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "A suite of helpers for writing cleaner React function components.",
|
5
|
+
"engines": {
|
6
|
+
"node": ">=18"
|
7
|
+
},
|
8
|
+
"files": [
|
9
|
+
"build"
|
10
|
+
],
|
11
|
+
"scripts": {
|
12
|
+
"build": "rimraf ./build && tsc && copyfiles README.md package.json .npmrc globals.d.ts tsconfig.json build",
|
13
|
+
"prepublishOnly": "npm run build",
|
14
|
+
"postpublish": "cd ./mirror-pkg && npm publish && cd ..",
|
15
|
+
"test": "echo \"No tests ATM\""
|
16
|
+
},
|
17
|
+
"keywords": [
|
18
|
+
"react",
|
19
|
+
"clean-react",
|
20
|
+
"clean react",
|
21
|
+
"function components",
|
22
|
+
"hooks",
|
23
|
+
"react hooks",
|
24
|
+
"state",
|
25
|
+
"clean state",
|
26
|
+
"group state"
|
27
|
+
],
|
28
|
+
"author": {
|
29
|
+
"name": "Feranmi Akinlade",
|
30
|
+
"url": "https://feranmi.dev"
|
31
|
+
},
|
32
|
+
"license": "MIT",
|
33
|
+
"devDependencies": {
|
34
|
+
"@types/node": "20.14.10",
|
35
|
+
"@types/react": "^16",
|
36
|
+
"copyfiles": "^2.4.1",
|
37
|
+
"rimraf": "^6.0.1",
|
38
|
+
"typescript": "^5.6.2"
|
39
|
+
},
|
40
|
+
"peerDependencies": {
|
41
|
+
"react": ">=16"
|
42
|
+
}
|
43
|
+
}
|