@webkrafters/react-observable-context 1.0.0 → 1.1.1
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 +68 -17
- package/dist/index.d.ts +18 -7
- package/dist/index.js +74 -22
- package/package.json +2 -1
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
|
|
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>
|
|
@@ -34,33 +46,58 @@ The context's `store` exposes **4** methods for interacting with the context's i
|
|
|
34
46
|
const ObservableContext = createContext();
|
|
35
47
|
export default ObservableContext;
|
|
36
48
|
|
|
49
|
+
<i><u>reset.js</u></i>
|
|
50
|
+
|
|
51
|
+
import React, { useContext } from 'react';
|
|
52
|
+
|
|
53
|
+
import ObservableContext from './context';
|
|
54
|
+
|
|
55
|
+
const Reset = () => {
|
|
56
|
+
|
|
57
|
+
const { resetState } = useContext( ObservableContext );
|
|
58
|
+
|
|
59
|
+
useEffect(() => console.log( 'Reset component rendered.....' ));
|
|
60
|
+
|
|
61
|
+
return ( <button onClick={ resetState }>reset context</button> );
|
|
62
|
+
};
|
|
63
|
+
Reset.displayName = 'Reset';
|
|
64
|
+
|
|
65
|
+
export default Reset;
|
|
66
|
+
|
|
37
67
|
<i><u>tally-display.js</u></i>
|
|
38
68
|
|
|
39
69
|
import React, { useContext, useEffect, useState } from 'react';
|
|
40
70
|
|
|
41
71
|
import ObservableContext from './context';
|
|
72
|
+
|
|
73
|
+
import Reset from './reset';
|
|
42
74
|
|
|
43
75
|
const TallyDisplay = () => {
|
|
44
76
|
|
|
45
77
|
const { getState, subscribe } = useContext( ObservableContext );
|
|
46
78
|
|
|
47
|
-
const [ ,
|
|
79
|
+
const [ , tripRender ] = useState( false );
|
|
48
80
|
|
|
49
81
|
useEffect(() => subscribe( newValue => {
|
|
50
82
|
[ 'color', 'price', 'type' ].some( k => k in newValue ) &&
|
|
51
|
-
|
|
83
|
+
tripRender( s => !s );
|
|
52
84
|
}), []);
|
|
53
85
|
|
|
54
86
|
useEffect(() => console.log( 'TallyDisplay component rendered.....' ));
|
|
55
87
|
|
|
56
88
|
return (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
89
|
+
<div>
|
|
90
|
+
<table>
|
|
91
|
+
<tbody>
|
|
92
|
+
<tr><td><label>Type:</label></td><td>{ getState( s => s.type ) }</td></tr>
|
|
93
|
+
<tr><td><label>Color:</label></td><td>{ getState( s => s.color ) }</td></tr>
|
|
94
|
+
<tr><td><label>Price:</label></td><td>{ getState( s => s.price ).toFixed( 2 ) }</td></tr>
|
|
95
|
+
</tbody>
|
|
96
|
+
</table>
|
|
97
|
+
<div style={{ textAlign: 'right' }}>
|
|
98
|
+
<Reset />
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
64
101
|
);
|
|
65
102
|
};
|
|
66
103
|
TallyDisplay.displayName = 'TallyDisplay';
|
|
@@ -130,11 +167,11 @@ The context's `store` exposes **4** methods for interacting with the context's i
|
|
|
130
167
|
|
|
131
168
|
const store = useContext( ObservableContext );
|
|
132
169
|
|
|
133
|
-
const [ ,
|
|
170
|
+
const [ , tripRender ] = useState( false );
|
|
134
171
|
|
|
135
172
|
useEffect(() => store.subscribe( newValue => {
|
|
136
173
|
( 'color' in newValue || 'type' in newValue ) &&
|
|
137
|
-
|
|
174
|
+
tripRender( s => !s );
|
|
138
175
|
} ), []);
|
|
139
176
|
|
|
140
177
|
useEffect(() => console.log( 'ProductDescription component rendered.....' ));
|
|
@@ -192,7 +229,7 @@ The context's `store` exposes **4** methods for interacting with the context's i
|
|
|
192
229
|
import ProductDescription from './product-description';
|
|
193
230
|
import TallyDisplay from './tally-display';
|
|
194
231
|
|
|
195
|
-
const Product = ({ type }) => {
|
|
232
|
+
const Product = ({ prehooks = undefined, type }) => {
|
|
196
233
|
|
|
197
234
|
const [ state, setState ] = useState(() => ({
|
|
198
235
|
color: 'Burgundy',
|
|
@@ -212,7 +249,11 @@ The context's `store` exposes **4** methods for interacting with the context's i
|
|
|
212
249
|
<div style={{ marginBottom: 10 }}>
|
|
213
250
|
<label>$ <input onKeyUp={ overridePricing } placeholder="override price here..."/></label>
|
|
214
251
|
</div>
|
|
215
|
-
|
|
252
|
+
<Provider
|
|
253
|
+
context={ ObservableContext }
|
|
254
|
+
prehooks={ prehooks }
|
|
255
|
+
value={ state }
|
|
256
|
+
>
|
|
216
257
|
<div style={{
|
|
217
258
|
borderBottom: '1px solid #333',
|
|
218
259
|
marginBottom: 10,
|
|
@@ -233,7 +274,7 @@ The context's `store` exposes **4** methods for interacting with the context's i
|
|
|
233
274
|
|
|
234
275
|
<i><u>app.js</u></i>
|
|
235
276
|
|
|
236
|
-
import React, { useCallback, useState } from 'react';
|
|
277
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
237
278
|
|
|
238
279
|
import Product from './product';
|
|
239
280
|
|
|
@@ -242,6 +283,17 @@ The context's `store` exposes **4** methods for interacting with the context's i
|
|
|
242
283
|
const [ productType, setProductType ] = useState( 'Calculator' );
|
|
243
284
|
|
|
244
285
|
const updateType = useCallback( e => setProductType( e.target.value ), [] );
|
|
286
|
+
|
|
287
|
+
const prehooks = React.useMemo(() => ({
|
|
288
|
+
resetState: ( ...args ) => {
|
|
289
|
+
console.log( 'resetting state with >>>> ', JSON.stringify( args ) );
|
|
290
|
+
return true;
|
|
291
|
+
},
|
|
292
|
+
setState: ( ...args ) => {
|
|
293
|
+
console.log( 'setting state with >>>> ', JSON.stringify( args ) );
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
}), []);
|
|
245
297
|
|
|
246
298
|
return (
|
|
247
299
|
<div className="App">
|
|
@@ -250,10 +302,9 @@ The context's `store` exposes **4** methods for interacting with the context's i
|
|
|
250
302
|
<div style={{ marginBottom: 10 }}>
|
|
251
303
|
<label>Type: <input onKeyUp={ updateType } placeholder="override product type here..." /></label>
|
|
252
304
|
</div>
|
|
253
|
-
<Product type={ productType } />
|
|
305
|
+
<Product prehooks={ prehooks } type={ productType } />
|
|
254
306
|
</div>
|
|
255
307
|
);
|
|
256
|
-
|
|
257
308
|
};
|
|
258
309
|
App.displayName = 'App';
|
|
259
310
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,30 +1,41 @@
|
|
|
1
1
|
export class UsageError extends Error {
|
|
2
2
|
}
|
|
3
|
-
export function createContext<
|
|
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:
|
|
17
|
+
context: ObservableContext<T>;
|
|
18
|
+
prehooks?: Prehooks<T>;
|
|
15
19
|
value: T;
|
|
16
20
|
}>;
|
|
17
|
-
export type ObservableContext<
|
|
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<
|
|
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<
|
|
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<
|
|
27
|
-
export type
|
|
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.
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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(
|
|
143
|
+
return selector(state);
|
|
103
144
|
}, []);
|
|
145
|
+
|
|
104
146
|
/** @type {Store<T>["resetState"]} */
|
|
105
147
|
var resetState = (0, _react.useCallback)(function () {
|
|
106
|
-
|
|
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
|
-
|
|
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.
|
|
78
|
+
"version": "1.1.1"
|
|
78
79
|
}
|