@webkrafters/react-observable-context 4.6.2 → 4.7.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 CHANGED
@@ -24,9 +24,9 @@
24
24
 
25
25
  <ul>
26
26
  <li> Update-friendly context.</li>
27
- <li> A context bearing an observable consumer [store](#store).</li>
27
+ <li> A context bearing an observable consumer <a href="#store">store</a>.</li>
28
28
  <li> Recognizes <b>negative array indexing</b>. Please see <a href="#property-path">Property Path</a> and <code>store.setState</code> <a href="#indexing">Indexing</a>.</li>
29
- <li> Only re-renders subscribing components ([clients](#client)) on context state changes.</li>
29
+ <li> Only re-renders subscribing components (<a href="#client">clients</a>) on context state changes.</li>
30
30
  <li> Subscribing component decides which context state properties' changes to trigger its update.</li>
31
31
  </ul>
32
32
 
@@ -418,13 +418,13 @@ The `React.Observable.Context` context `store` is the client's portal into the c
418
418
  <li>
419
419
  <p style="margin: 0 0 0 10px">
420
420
  <a href="#store-resetstate"><b>resetState:</b></a>
421
- <code style="margin-left: 5px">(propertyPaths?: Array<string>) => void // resets slices of state referenced by the property paths to their initial values.</code>
421
+ <code style="margin-left: 5px">(propertyPaths?: Array&lt;string&gt;) => void // resets slices of state referenced by the property paths to their initial values.</code>
422
422
  </p>
423
423
  </li>
424
424
  <li>
425
425
  <p style="margin: 0 0 0 10px">
426
426
  <a href="#store-setstate"><b>setState:</b></a>
427
- <code style="margin-left: 5px">(changes: PartialState<State>) => void // merges only new/changed state slices.</code>
427
+ <code style="margin-left: 5px">(changes: Changes&lt;State&gt;) => void // merges only new/changed state slices.</code>
428
428
  </p>
429
429
  </li>
430
430
  </ol>
@@ -437,14 +437,29 @@ The `React.Observable.Context` context `store` is the client's portal into the c
437
437
  <span style="margin: 5px 10px 0 0">-</span>Performs no state reset when a client with no selector map invokes this method with 0 arguments.
438
438
 
439
439
  <h3 id="store-setstate" style="margin-top:10px"><code>store.setState</code> Usage</h3>
440
- <blockquote>[This store's] internal state is <u><b>immutable</b></u> and <u><b>private</b></u>.</blockquote>
441
- New updates are merged into state by default. To overwrite state, use the <a href="setstate-tags">tag</a> command.<br />
442
- :warning: <b><i>Do this:</i></b> <code>setState({stateKey0: changes0[, ...]});</code><br />
443
- :warning: <b><i>Not this:</i></b> <code>setState({stateKey0: {...state.stateKey0, ...changes0}[, ...]});</code>
444
- <h3 id="indexing"><b><i><u>Indexing</u></i></b></h3>
445
- Existing array state property can be overridden with a new array.<br />
446
- Use the indexed object to update array content at indexes.<br />
447
- Indexed object recognizes negative indexing. See additional <a href="#neg-idx-tip">tip</a> below.<br />
440
+
441
+ <blockquote>[This store's] internal state is <u><b>immutable</b></u> and <u><b>private</b></u>.<br />Direct mutation attempts on its properties have no effect.</blockquote>
442
+ New updates are merged into state by default. So only supply the exact changes to be merged <b><i>(i.e. do not spread the new state changes into the current state as is commonly done in React development)</i></b>. And to overwrite a slice of state, use the <a href="#setstate-tags">tag</a> command.<br />
443
+ :warning: <b><i>Do this:</i></b> <code>setState({stateKey0: changes0});</code><br />
444
+ :warning: <b><i>Not this:</i></b> <code>setState({...state, stateKey0: {...state.stateKey0, ...changes0}});</code><br />
445
+
446
+ <h3 id="batched-update"><i><u>Batched update</u></i></h3>
447
+ provides a way to update the state as a transaction of several state changes. This can be achieved by collecting a series of state changes in an array and passing that array as an argument to the <code>store.setState</code> method. The state changes are resolved sequentially from <code>index 0</code> to the <code>last index</code>. <a href="#client">Clients</a> are only notified at batched update completion.<br />
448
+ :warning: <b><i>Do this:</i></b> <code>setState([<br />
449
+ &nbsp;&nbsp;&nbsp;&nbsp;{stateKey0: changes0},<br />
450
+ &nbsp;&nbsp;&nbsp;&nbsp;{stateKey1: changes1},<br />
451
+ &nbsp;&nbsp;&nbsp;&nbsp;// et cetera ... et cetera<br />
452
+ ]);</code><br />
453
+ :warning: <b><i>Not this:</i></b> <code>setState([<br />
454
+ &nbsp;&nbsp;&nbsp;&nbsp;{...state, stateKey0: {...state.stateKey0, ...changes0}},<br />
455
+ &nbsp;&nbsp;&nbsp;&nbsp;{...state, stateKey1: {...state.stateKey1, ...changes1}},<br />
456
+ &nbsp;&nbsp;&nbsp;&nbsp;// et cetera ... et cetera<br />
457
+ ]);</code>
458
+
459
+ <h3 id="indexing"><i><u>Indexing</u></i></h3>
460
+ Traditionally, array state properties are updated by a new array replacement. This overwrites the existing state property.<br />
461
+ Hence the need for `indexing`. Indexing provides a mechanism for updating array state properties at specific indexes using an indexed state change object.<br />
462
+ The store also recognizes and resolves negative indexes when present in the indexed state change object. See additional <a href="#neg-idx-tip">tip</a> below.<br />
448
463
  <strong>Example:</strong>
449
464
 
450
465
  ```jsx
@@ -573,7 +588,7 @@ store.setState({ a: { [ PUSH_TAG ]: [{ n: 5 }] } }) // assigning a '@@PUSH' comm
573
588
  store.setState({ a: { b: { [ PUSH_TAG ]: [{ x: 27, y: 28, z: 29 }, { x: 37, y: 38, z: 39 }] } } })
574
589
  ```
575
590
 
576
- <i id="replace-tag-usage"><b>@@REPLACE:</b> (takes an argument holding the replacment value)</i>
591
+ <i id="replace-tag-usage"><b>@@REPLACE:</b> (takes an argument holding the replacement value)</i>
577
592
 
578
593
  ```jsx
579
594
  import { REPLACE_TAG } from '@webkrafters/react-observable-context'; // REPLACE_TAG = "@@REPLACE"
@@ -623,7 +638,7 @@ store.setState({ a: { b: [ state.a.b[ 0 ], { [ SET_TAG ]: currentValue => ({ ...
623
638
  store.setState({ a: { b: { 1: { [ SET_TAG ]: currentValue => ({ ...currentValue, x: 97, y: 98, z: 99 }) } } } })
624
639
  ```
625
640
 
626
- <i id="splice-tag-usage"><b>@@SPLICE:</b> (takes an array argumenst listing: -/+fromIndex, +deleteCount and optional ...newItems? newItems = ...[] by default)</i>
641
+ <i id="splice-tag-usage"><b>@@SPLICE:</b> (takes an array argument listing: -/+fromIndex, +deleteCount and optional ...newItems? newItems = ...[] by default)</i>
627
642
 
628
643
  ```jsx
629
644
  import { SPLICE_TAG } from '@webkrafters/react-observable-context'; // SPLICE_TAG = "@@SPLICE"
@@ -683,6 +698,10 @@ store.setState({
683
698
  <h1 id="changes">What's Changed?</h1>
684
699
 
685
700
  <table>
701
+ <thead><tr><th>v4.7.0</th></tr></thead>
702
+ <tbody>
703
+ <tr><td><b>1.</b></td><td><a href="#store-setstate"><code>store.setState</code></a> can now accept an array of updates for gurranteed orderly processing.</td></tr>
704
+ </tbody>
686
705
  <thead><tr><th>v4.6.0</th></tr></thead>
687
706
  <tbody>
688
707
  <tr><td><b>1.</b></td><td><a href="#store-resetstate"><code>store.resetState</code></a> can now update reset current state even when initial state does not exist. Formerly, a resetState call on a non-existent initial state had no effect.</td></tr>
@@ -25,7 +25,7 @@ var useStateManager = function useStateManager(initStateValue) {
25
25
  _useState4 = _slicedToArray(_useState3, 1),
26
26
  cache = _useState4[0];
27
27
  var select = (0, _react.useCallback)(cache.get.bind(cache), []);
28
- var stateWatch = (0, _react.useCallback)(cache.watchSource.bind(cache), []);
28
+ var stateWatch = (0, _react.useCallback)(cache.atomize.bind(cache), []);
29
29
  var unlink = (0, _react.useCallback)(function (clientId) {
30
30
  return cache.unlinkClient(clientId);
31
31
  }, []);
@@ -4,7 +4,7 @@ export namespace deps {
4
4
  }
5
5
  export default useStore;
6
6
  export type IStorage<T extends import("../../../types").State> = import("../../../types").IStorage<T>;
7
- export type UpdatePayload<T extends import("../../../types").State> = import("../../../types").UpdatePayload<PartialState<T>>;
7
+ export type Changes<T extends import("../../../types").State> = import("../../../types").Changes<T>;
8
8
  export type PartialState<T extends import("../../../types").State> = import('../../../types').PartialState<T>;
9
9
  export type Prehooks<T extends import("../../../types").State> = import("../../../types").Prehooks<T>;
10
10
  export type StoreInternal<T extends import("../../../types").State> = import("../../../types").StoreInternal<T>;
@@ -116,8 +116,9 @@ var useContext = function useContext(context) {
116
116
  try {
117
117
  for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
118
118
  var path = _step2.value;
119
- if (data[selectorMapInverse[path]] !== state[path]) {
120
- data[selectorMapInverse[path]] = state[path];
119
+ var dPath = selectorMapInverse[path];
120
+ if (data[dPath] !== state[path]) {
121
+ data[dPath] = state[path];
121
122
  hasChanges = true;
122
123
  }
123
124
  }
@@ -134,7 +135,8 @@ var useContext = function useContext(context) {
134
135
  }, []);
135
136
  _react["default"].useEffect(function () {
136
137
  if ((0, _lodash["default"])(_renderKeys)) {
137
- !(0, _lodash2["default"])({}, data) && setData({});
138
+ var _default = {};
139
+ !(0, _lodash2["default"])(_default, data) && setData(_default);
138
140
  return;
139
141
  }
140
142
  for (var selectorKey in data) {
@@ -25,8 +25,7 @@ export type HasRoot<K extends import("../../types").KeyType = string, T> = K ext
25
25
  };
26
26
  export type KeyType = import("../../types").KeyType;
27
27
  export type Listener<T extends import("../../types").State> = import("../../types").Listener<T>;
28
- export type UpdatePayload<T> = import("../../types").UpdatePayload<T>;
29
- export type PartialState<T extends import("../../types").State> = import("../../types").PartialState<T>;
28
+ export type Changes<T> = import("../../types").Changes<T>;
30
29
  export type State = import("../../types").State;
31
30
  export type Stats = import("../../types").UpdateStats;
32
- declare function setState<T extends import("../../types").State>(state: T, changes: UpdatePayload<import("../../types").PartialState<T>>, onStateChange?: Listener<T>): void;
31
+ declare function setState<T extends import("../../types").State>(state: T, changes: Changes<T>, onStateChange?: Listener<T>): void;
@@ -11,6 +11,7 @@ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "functio
11
11
  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; }
12
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
13
13
  function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
14
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
14
15
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
15
16
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
16
17
  function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
@@ -182,11 +183,29 @@ function setState(state, changes, onStateChange) {
182
183
  var stats = {
183
184
  hasChanges: false
184
185
  };
185
- var changeRequest = {
186
- state: (0, _utils.clonedeep)(changes)
187
- };
188
- set({
189
- state: state
190
- }, changeRequest, stats);
186
+ if (!Array.isArray(changes)) {
187
+ set({
188
+ state: state
189
+ }, {
190
+ state: (0, _utils.clonedeep)(changes)
191
+ }, stats);
192
+ } else {
193
+ var _iterator = _createForOfIteratorHelper(changes),
194
+ _step;
195
+ try {
196
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
197
+ var _cGroup = _step.value;
198
+ set({
199
+ state: state
200
+ }, {
201
+ state: (0, _utils.clonedeep)(_cGroup)
202
+ }, stats);
203
+ }
204
+ } catch (err) {
205
+ _iterator.e(err);
206
+ } finally {
207
+ _iterator.f();
208
+ }
209
+ }
191
210
  stats.hasChanges && (onStateChange === null || onStateChange === void 0 ? void 0 : onStateChange(changes));
192
211
  }
@@ -3,10 +3,10 @@ export type Listener<T extends import("../../types").State> = import("../../type
3
3
  export type State = import("../../types").State;
4
4
  declare class AccessorCache<T extends import("../../types").State> {
5
5
  constructor(origin: T);
6
+ atomize(changes: import("../../types").Changes<T>): void;
6
7
  get(clientId: string, ...propertyPaths: string[]): {
7
8
  [propertyPaths: string]: Readonly<any>;
8
9
  };
9
10
  unlinkClient(clientId: string): void;
10
- watchSource(changes: import("../../types").Changes<T>): void;
11
11
  #private;
12
12
  }
@@ -55,9 +55,50 @@ var AccessorCache = function () {
55
55
  _classPrivateFieldSet(this, _origin, origin);
56
56
  }
57
57
  _createClass(AccessorCache, [{
58
+ key: "atomize",
59
+ value: function atomize(originChanges) {
60
+ var accessors = _classPrivateFieldGet(this, _accessors);
61
+ var atoms = _classPrivateFieldGet(this, _atoms);
62
+ var updatedPaths = [];
63
+ for (var path in atoms) {
64
+ var _classPrivateMethodGe = _classPrivateMethodGet(this, _getOriginAt, _getOriginAt2).call(this, path),
65
+ exists = _classPrivateMethodGe.exists,
66
+ newAtomVal = _classPrivateMethodGe.value;
67
+ if (path !== _constants.FULL_STATE_SELECTOR && exists && (newAtomVal === null || newAtomVal === undefined)) {
68
+ if (!Array.isArray(originChanges)) {
69
+ if (!(0, _utils.getProperty)(originChanges, path).trail.length) {
70
+ continue;
71
+ }
72
+ } else {
73
+ var found = false;
74
+ for (var i = originChanges.length; i--;) {
75
+ if ((0, _utils.getProperty)(originChanges, "".concat(i, ".").concat(path)).trail.length) {
76
+ found = true;
77
+ break;
78
+ }
79
+ }
80
+ if (!found) {
81
+ continue;
82
+ }
83
+ }
84
+ }
85
+ if ((0, _lodash2["default"])(newAtomVal, atoms[path].value)) {
86
+ continue;
87
+ }
88
+ atoms[path].setValue(newAtomVal);
89
+ updatedPaths.push(path);
90
+ }
91
+ if (!updatedPaths.length) {
92
+ return;
93
+ }
94
+ for (var k in accessors) {
95
+ var _accessors$k$outdated;
96
+ (_accessors$k$outdated = accessors[k].outdatedPaths).push.apply(_accessors$k$outdated, updatedPaths);
97
+ }
98
+ }
99
+ }, {
58
100
  key: "get",
59
- value:
60
- function get(clientId) {
101
+ value: function get(clientId) {
61
102
  for (var _len = arguments.length, propertyPaths = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
62
103
  propertyPaths[_key - 1] = arguments[_key];
63
104
  }
@@ -96,30 +137,6 @@ var AccessorCache = function () {
96
137
  delete accessors[k];
97
138
  }
98
139
  }
99
- }, {
100
- key: "watchSource",
101
- value: function watchSource(originChanges) {
102
- var accessors = _classPrivateFieldGet(this, _accessors);
103
- var atoms = _classPrivateFieldGet(this, _atoms);
104
- var updatedPaths = [];
105
- for (var path in atoms) {
106
- var _classPrivateMethodGe = _classPrivateMethodGet(this, _getOriginAt, _getOriginAt2).call(this, path),
107
- exists = _classPrivateMethodGe.exists,
108
- newAtomVal = _classPrivateMethodGe.value;
109
- if (path !== _constants.FULL_STATE_SELECTOR && exists && (newAtomVal === null || newAtomVal === 'undefined') && !(0, _utils.getProperty)(originChanges, path).trail.length || (0, _lodash2["default"])(newAtomVal, atoms[path].value)) {
110
- continue;
111
- }
112
- atoms[path].setValue(newAtomVal);
113
- updatedPaths.push(path);
114
- }
115
- if (!updatedPaths.length) {
116
- return;
117
- }
118
- for (var k in accessors) {
119
- var _accessors$k$outdated;
120
- (_accessors$k$outdated = accessors[k].outdatedPaths).push.apply(_accessors$k$outdated, updatedPaths);
121
- }
122
- }
123
140
  }]);
124
141
  return AccessorCache;
125
142
  }();
package/dist/types.d.ts CHANGED
@@ -53,10 +53,11 @@ export type Store<T extends State, SELECTOR_MAP extends BaseSelectorMap<T> = Bas
53
53
  resetState: (propertyPaths?: string[]) => void;
54
54
  setState: (changes: Changes<T>) => void;
55
55
  };
56
- export type Changes<T extends State> = UpdatePayload<PartialState<T>>;
56
+ export type Changes<T extends State> = UpdatePayload<PartialState<T>> | UpdatePayloadArray<PartialState<T>>;
57
57
  export type UpdateStats = {
58
58
  hasChanges: boolean;
59
59
  };
60
+ export type UpdatePayloadArray<T> = Array<UpdatePayload<T>>;
60
61
  export type UpdatePayload<T> = "@@CLEAR" | T | ClearCommand | DeleteCommand<T> | MoveCommand | PushCommand | ReplaceCommand | SetCommand<T> | SpliceCommand | { [K in keyof T]?: UpdatePayload<T[K]>; };
61
62
  export type ClearCommand = {
62
63
  "@@CLEAR": any;
@@ -5,6 +5,8 @@ export function mapPathsToObject<T extends {
5
5
  [x: string]: any;
6
6
  }>(source: T, propertyPaths: Array<string>, transform?: Tranform): { [K in keyof T]?: any; };
7
7
  export function clonedeep<T, R>(value: T): R;
8
- export const getProperty: any;
8
+ export const getProperty: typeof get;
9
9
  export const stringToDotPath: (path: string) => string;
10
- export type Tranform = (property: any) => T;
10
+ export type Tranform<T = any> = (property: PropertyInfo) => T;
11
+ export type PropertyInfo = import("@webkrafters/get-property").PropertyInfo;
12
+ import get from "@webkrafters/get-property";
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "steveswork <stephen.isienyi@gmail.com> (https://github.com/steveswork)"
8
8
  ],
9
9
  "dependencies": {
10
- "@webkrafters/get-property": "^1.1.1",
10
+ "@webkrafters/get-property": "^1.1.2",
11
11
  "lodash.clonedeepwith": "^4.5.0",
12
12
  "lodash.isboolean": "^3.0.3",
13
13
  "lodash.isempty": "^4.4.0",
@@ -133,5 +133,5 @@
133
133
  "test:watch": "eslint --fix && jest --updateSnapshot --watchAll"
134
134
  },
135
135
  "types": "dist/main/index.d.ts",
136
- "version": "4.6.2"
136
+ "version": "4.7.1"
137
137
  }