@webkrafters/react-observable-context 1.0.0 → 1.1.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.
package/README.md CHANGED
@@ -14,7 +14,9 @@ The React-Observable-Context package exports only **2** modules namely: the **cr
14
14
 
15
15
  `createContext` is a zero-parameter funtion returning a store-bearing context. Pass the context to the React::useContext() parameter to obtain the context's `store`.
16
16
 
17
- The `Provider` can immediately be used as-is anywhere the React-Observable-Context is required. Supply the context to its `context` prop and the initial state to its `value` prop as is customary to React::Provider.
17
+ The `Provider` can immediately be used as-is anywhere the React-Observable-Context is required. It accepts **3** props and the customary Provider `children` prop. Supply the context to its `context` prop; the initial state to the customary Provider `value` prop; and the optional `prehooks` props <i>(discussed in the prehooks section below)</i>.
18
+
19
+ <i><u>Note:</u></i> the Provider `context` prop is not updateable. Once set, all further updates to this prop are ignored.
18
20
 
19
21
  The context's `store` exposes **4** methods for interacting with the context's internal state namely:
20
22
 
@@ -26,6 +28,16 @@ The context's `store` exposes **4** methods for interacting with the context's i
26
28
 
27
29
  * **subscribe**: (listener: (newValue: PartialState\<State\>, oldValue: PartialState\<State\>) => void) => ***UnsubscribeFunction***
28
30
 
31
+ ### Prehooks
32
+
33
+ The context's store update operation adheres to **2** user supplied prehooks when present. Otherwise, the update operation proceeds normally to completion. They are named **resetState** and **setState** after the store update methods which utilize them.
34
+
35
+ * **resetState**: (state: {current: State, original: State}) => boolean
36
+
37
+ * **setState**: (newChanges: PartialState\<State\>) => boolean
38
+
39
+ **usecase**: prehooks provide a central place for sanitizing, modifying, transforming, validating etc. all related incoming state updates. The prehook returns a **boolean** value (`true` to continue OR `false` to abort the update operation). The prehook may mutate (i.e. sanitize, transform, transpose) its argument values to accurately reflect the intended update value.
40
+
29
41
  ## Usage
30
42
 
31
43
  <i><u>context.js</u></i>
package/dist/index.d.ts CHANGED
@@ -1,30 +1,41 @@
1
1
  export class UsageError extends Error {
2
2
  }
3
- export function createContext<T extends State>(): import("react").Context<Store<T>>;
3
+ export function createContext<T_1 extends State>(): import("react").Context<Store<T_1>>;
4
4
  /**
5
+ * Note: `context` prop is not updateable. Furtther updates to this prop are ignored.
6
+ *
5
7
  * @type {import("react").FC<{
6
8
  * children?: import("react").ReactNode,
7
9
  * context: ObservableContext<T>,
10
+ * prehooks?: Prehooks<T>
8
11
  * value: T
9
12
  * }>}
10
13
  * @template {State} T
11
14
  */
12
15
  export const Provider: import("react").FC<{
13
16
  children?: import("react").ReactNode;
14
- context: import("react").Context<Store<T>>;
17
+ context: ObservableContext<T>;
18
+ prehooks?: Prehooks<T>;
15
19
  value: T;
16
20
  }>;
17
- export type ObservableContext<T extends State> = import("react").Context<Store<T>>;
21
+ export type ObservableContext<T_1 extends State> = import("react").Context<Store<T>>;
18
22
  export type OptionalTask<F = void> = F extends void ? () => never : F;
19
- export type Listener<T extends State> = (newValue: PartialState<T>, oldValue: PartialState<T>) => void;
23
+ export type Listener<T_1 extends State> = (newValue: PartialState<T>, oldValue: PartialState<T>) => void;
20
24
  export type State = {
21
25
  [x: string]: any;
22
26
  };
23
- export type PartialState<T extends State> = {
27
+ export type PartialState<T_1 extends State> = {
24
28
  [x: string]: any;
25
29
  } & { [K in keyof T]?: T[K]; };
26
- export type Selector<T extends State> = (state: T) => any;
27
- export type Store<T extends State> = {
30
+ export type Selector<T_1 extends State> = (state: T) => any;
31
+ export type Prehooks<T_1 extends State> = {
32
+ resetState?: (state: {
33
+ current: T;
34
+ original: T;
35
+ }) => boolean;
36
+ setState?: (newChanges: PartialState<T>) => boolean;
37
+ };
38
+ export type Store<T_1 extends State> = {
28
39
  getState: (selector?: Selector<T>) => any;
29
40
  resetState: OptionalTask<VoidFunction>;
30
41
  setState: (changes: PartialState<T>) => void;
package/dist/index.js CHANGED
@@ -6,7 +6,8 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.createContext = exports.UsageError = exports.Provider = void 0;
8
8
  var _react = _interopRequireWildcard(require("react"));
9
- var _lodash = _interopRequireDefault(require("lodash.isempty"));
9
+ var _lodash = _interopRequireDefault(require("lodash.clonedeep"));
10
+ var _lodash2 = _interopRequireDefault(require("lodash.isempty"));
10
11
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
11
12
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
12
13
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
@@ -66,61 +67,96 @@ var createContext = function createContext() {
66
67
  };
67
68
 
68
69
  /**
70
+ * @readonly
71
+ * @type {Prehooks<T>}
72
+ * @template {State} T
73
+ */
74
+ exports.createContext = createContext;
75
+ var defaultPrehooks = Object.freeze({});
76
+
77
+ /**
78
+ * @param {T} state
79
+ * @param {PartialState<T>} newState
80
+ * @param {Listener<T>} onStateChange
81
+ * @template {State} T
82
+ */
83
+ var _setState = function _setState(state, newState, onStateChange) {
84
+ /** @type {PartialState<T>} */
85
+ var newChanges = {};
86
+ /** @type {PartialState<T>} */
87
+ var replacedValue = {};
88
+ for (var k in newState) {
89
+ if (state[k] === newState[k]) {
90
+ continue;
91
+ }
92
+ replacedValue[k] = state[k];
93
+ state[k] = newState[k];
94
+ newChanges[k] = newState[k];
95
+ }
96
+ !(0, _lodash2["default"])(newChanges) && onStateChange(newChanges, replacedValue);
97
+ };
98
+
99
+ /**
100
+ * Note: `context` prop is not updateable. Furtther updates to this prop are ignored.
101
+ *
69
102
  * @type {import("react").FC<{
70
103
  * children?: import("react").ReactNode,
71
104
  * context: ObservableContext<T>,
105
+ * prehooks?: Prehooks<T>
72
106
  * value: T
73
107
  * }>}
74
108
  * @template {State} T
75
109
  */
76
- exports.createContext = createContext;
77
110
  var Provider = function Provider(_ref) {
78
111
  var _ref$children = _ref.children,
79
112
  children = _ref$children === void 0 ? null : _ref$children,
80
113
  context = _ref.context,
114
+ _ref$prehooks = _ref.prehooks,
115
+ prehooks = _ref$prehooks === void 0 ? defaultPrehooks : _ref$prehooks,
81
116
  value = _ref.value;
82
- var valueRef = (0, _react.useRef)(value);
83
- /** @type {ObservableContext<T>} */
84
- var _useState = (0, _react.useState)(context),
117
+ var prehooksRef = (0, _react.useRef)(prehooks);
118
+ var initialState = (0, _react.useRef)(value);
119
+
120
+ /** @type {[T, Function]} */
121
+ var _useState = (0, _react.useState)(function () {
122
+ return (0, _lodash["default"])(value);
123
+ }),
85
124
  _useState2 = _slicedToArray(_useState, 1),
86
- StoreContext = _useState2[0];
125
+ state = _useState2[0];
87
126
  /** @type {[Set<Listener<T>>, Function]} */
88
127
  var _useState3 = (0, _react.useState)(function () {
89
128
  return new Set();
90
129
  }),
91
130
  _useState4 = _slicedToArray(_useState3, 1),
92
131
  listeners = _useState4[0];
132
+
93
133
  /** @type {Listener<T>} */
94
134
  var onChange = function onChange(newValue, oldValue) {
95
135
  return listeners.forEach(function (listener) {
96
136
  return listener(newValue, oldValue);
97
137
  });
98
138
  };
139
+
99
140
  /** @type {Store<T>["getState"]} */
100
141
  var getState = (0, _react.useCallback)(function () {
101
142
  var selector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultSelector;
102
- return selector(valueRef.current);
143
+ return selector(state);
103
144
  }, []);
145
+
104
146
  /** @type {Store<T>["resetState"]} */
105
147
  var resetState = (0, _react.useCallback)(function () {
106
- return setState(value);
148
+ var original = (0, _lodash["default"])(initialState.current);
149
+ (!('resetState' in prehooksRef.current) || prehooksRef.current.resetState({
150
+ current: (0, _lodash["default"])(state),
151
+ original: original
152
+ })) && _setState(state, original, onChange);
107
153
  }, []);
154
+
108
155
  /** @type {Store<T>["setState"]} */
109
156
  var setState = (0, _react.useCallback)(function (changes) {
110
- /** @type {PartialState<T>} */
111
- var newChanges = {};
112
- /** @type {PartialState<T>} */
113
- var replacedValue = {};
114
- for (var k in changes) {
115
- if (valueRef.current[k] === changes[k]) {
116
- continue;
117
- }
118
- replacedValue[k] = valueRef.current[k];
119
- valueRef.current[k] = changes[k];
120
- newChanges[k] = changes[k];
121
- }
122
- !(0, _lodash["default"])(newChanges) && onChange(newChanges, replacedValue);
157
+ (!('setState' in prehooksRef.current) || prehooksRef.current.setState(changes)) && _setState(state, changes, onChange);
123
158
  }, []);
159
+
124
160
  /** @type {Store<T>["subscribe"]} */
125
161
  var subscribe = (0, _react.useCallback)(function (listener) {
126
162
  listeners.add(listener);
@@ -129,9 +165,13 @@ var Provider = function Provider(_ref) {
129
165
  };
130
166
  }, []);
131
167
  (0, _react.useEffect)(function () {
132
- return setState(value);
168
+ return setState((0, _lodash["default"])(value));
133
169
  }, [value]);
170
+ (0, _react.useEffect)(function () {
171
+ prehooksRef.current = prehooks;
172
+ }, [prehooks]);
134
173
  /** @type {[Store<T>, Function]} */
174
+
135
175
  var _useState5 = (0, _react.useState)(function () {
136
176
  return {
137
177
  getState: getState,
@@ -142,6 +182,10 @@ var Provider = function Provider(_ref) {
142
182
  }),
143
183
  _useState6 = _slicedToArray(_useState5, 1),
144
184
  store = _useState6[0];
185
+ /** @type {ObservableContext<T>} */
186
+ var _useState7 = (0, _react.useState)(context),
187
+ _useState8 = _slicedToArray(_useState7, 1),
188
+ StoreContext = _useState8[0];
145
189
  return /*#__PURE__*/_react["default"].createElement(StoreContext.Provider, {
146
190
  value: store
147
191
  }, children);
@@ -176,6 +220,14 @@ Provider.displayName = 'ObservableContext.Provider';
176
220
  * @template {State} T
177
221
  */
178
222
 
223
+ /**
224
+ * @typedef {{
225
+ * resetState?: (state: { current: T, original: T}) => boolean,
226
+ * setState?: (newChanges: PartialState<T>) => boolean
227
+ * }} Prehooks
228
+ * @template {State} T
229
+ */
230
+
179
231
  /**
180
232
  * @typedef {{
181
233
  * getState: OptionalTask<(selector?: Selector<T>) => *>,
package/package.json CHANGED
@@ -8,6 +8,7 @@
8
8
  ],
9
9
  "dependencies": {
10
10
  "@types/react": "^18.0.17",
11
+ "lodash.clonedeep": "^4.5.0",
11
12
  "lodash.isempty": "^4.4.0",
12
13
  "react": "^18.2.0"
13
14
  },
@@ -74,5 +75,5 @@
74
75
  "test:watch": "eslint --fix && jest --watchAll"
75
76
  },
76
77
  "types": "dist/index.d.ts",
77
- "version": "1.0.0"
78
+ "version": "1.1.0"
78
79
  }