@webkrafters/react-observable-context 4.0.0-rc.0 → 4.0.0-rc.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 +78 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,13 +29,14 @@ Subscribing component decides which context state properties' changes to trigger
|
|
|
29
29
|
|
|
30
30
|
**Usage:** Please see [Usage](#usage) section
|
|
31
31
|
|
|
32
|
-
**Demo:** [Play with the app on codesandbox](https://codesandbox.io/s/github/webKrafters/react-observable-context-app)
|
|
32
|
+
**Demo:** [Play with the app on codesandbox](https://codesandbox.io/s/github/webKrafters/react-observable-context-app)\
|
|
33
|
+
If sandbox fails to load app, please refresh dependencies on its lower left.
|
|
33
34
|
|
|
34
35
|
**Install:**\
|
|
35
36
|
npm i -S @webkrafters/react-observable-context\
|
|
36
37
|
npm install --save @webkrafters/react-observable-context
|
|
37
38
|
|
|
38
|
-
May also see
|
|
39
|
+
May also see <b><a href="#changes">What's Changed?</a></b> section below.
|
|
39
40
|
|
|
40
41
|
# Intro
|
|
41
42
|
|
|
@@ -89,15 +90,18 @@ The property path `a.c.e` accesses the `e=5` property.<br />
|
|
|
89
90
|
Either of the property paths `a.c.f.1` and `a.c.f[1]` accesses the `[1]=2` property.<br />
|
|
90
91
|
A special property path [@@STATE](#fullstate-selectorkey) may be used to access the full given object.<br />
|
|
91
92
|
|
|
92
|
-
<strong id="fullstate-selectorkey"><u>@@STATE</u></strong> is a special property path to access the full state object as a single slice
|
|
93
|
+
<strong id="fullstate-selectorkey"><u>@@STATE</u></strong> is a special property path to access the full state object as a single slice.<br />
|
|
94
|
+
***Caution:*** When this property path exists in a <a href="#selector-map">selector map</a>, any change in the state object results in an update of its <a href="#store"><code>store.data</code></a> and a subsequent render of its client(s).
|
|
93
95
|
|
|
94
96
|
## Provider
|
|
95
97
|
The Provider component is a property of the `React-Observable-Context` context object. As a `React.context` based provider, it accepts the customary `children` and `value` props. It also accepts **2** optional props: <a href="#prehooks"><code>prehooks</code></a> and <a href="#storage"><code>storage</code></a>.
|
|
96
98
|
|
|
99
|
+
Routinely, the `value` prop is initialized with the full initial state. It may only be updated with parts of the state which are changing. Please see a [Provider Usage](#provider-usage) sample below.
|
|
100
|
+
|
|
97
101
|
<h2 id="selector-map">Selector Map</h2>
|
|
98
102
|
A selector map is an object holding key:value pairs.<br />
|
|
99
|
-
<span style="margin-right: 10px">-</span><code>key</code> refers to an arbitrary name to be assigned to a given property in the <code>store.data</code>.<br />
|
|
100
|
-
<span style="margin-right: 10px">-</span><code>value</code> refers to the <a href="#property-path">property path</a> leading to a state slice whose value will be assigned to and observed by this <code>store.data</code> property.<br />
|
|
103
|
+
<span style="margin-right: 10px">-</span><code>key</code> refers to an arbitrary name to be assigned to a given property in the <a href="#store"><code>store.data</code></a>.<br />
|
|
104
|
+
<span style="margin-right: 10px">-</span><code>value</code> refers to the <a href="#property-path">property path</a> leading to a state slice whose value will be assigned to and observed by this <a href="#store"><code>store.data</code></a> property.<br />
|
|
101
105
|
<span style="margin-right: 10px">-</span>A special '<a href="#fullstate-selectorkey">@@STATE</a>' value may be used to access and observe the full state object.<br />
|
|
102
106
|
|
|
103
107
|
<strong id="selector-map-example">Example:</strong>
|
|
@@ -132,7 +136,7 @@ store.data = {
|
|
|
132
136
|
```
|
|
133
137
|
|
|
134
138
|
## Storage
|
|
135
|
-
The `React.Observable.Context` context allows for a user-defined Storage object to
|
|
139
|
+
The `React.Observable.Context` context allows for a user-defined Storage object to be provided for maintaining the integrity of the initial context state at a location of the user's choosing. This, it accepts, via its Provider's `storage` optional prop. The context defaults to `window.sessionstorage` in supporting environments. Otherwise, it defaults to its own internal memory-based storage.
|
|
136
140
|
|
|
137
141
|
A valid storage object is of the type: `IStorage<State>` implementing the following **4** methods:
|
|
138
142
|
<ol>
|
|
@@ -143,7 +147,7 @@ A valid storage object is of the type: `IStorage<State>` implementing the follow
|
|
|
143
147
|
</ol>
|
|
144
148
|
|
|
145
149
|
## Store
|
|
146
|
-
The `React.Observable.Context` context `store` is the client's
|
|
150
|
+
The `React.Observable.Context` context `store` is the client's portal into the context's underlying state. It exposes **3** properties namely:
|
|
147
151
|
<ol>
|
|
148
152
|
<li>
|
|
149
153
|
<p style="margin: 0 0 0 10px">
|
|
@@ -245,28 +249,26 @@ The React-Observable-Context module contains **4** exports namely:
|
|
|
245
249
|
|
|
246
250
|
# Usage
|
|
247
251
|
|
|
248
|
-
|
|
252
|
+
<i><b><u>context.js</u></b></i>
|
|
253
|
+
```
|
|
254
|
+
import { connect, createContext, useContext } from '@webkrafters/react-observable-context';
|
|
255
|
+
|
|
256
|
+
const ObservableContext = createContext();
|
|
257
|
+
|
|
258
|
+
export const connectObservableContext = selectorMap => connect( ObservablContext, selectorMap );
|
|
249
259
|
|
|
250
|
-
|
|
251
|
-
const ObservableContext = createContext();
|
|
252
|
-
export const connectObservableContext = selectorMap => connect( ObservablContext, selectorMap );
|
|
253
|
-
export const useObservableContext = selectorMap => useContext( ObservableContext, selectorMap );
|
|
254
|
-
export default ObservableContext;
|
|
260
|
+
export const useObservableContext = selectorMap => useContext( ObservableContext, selectorMap );
|
|
255
261
|
|
|
256
|
-
|
|
262
|
+
export default ObservableContext;
|
|
257
263
|
```
|
|
258
|
-
/********************************************/
|
|
259
|
-
/* ui.js: using the `connect` HOC method. */
|
|
260
|
-
/********************************************/
|
|
261
264
|
|
|
265
|
+
<i><b><u>ui.js</u></b> (connect method)</i>
|
|
266
|
+
```
|
|
262
267
|
import React, { useCallback, useEffect } from 'react';
|
|
263
268
|
import ObservableContext, { connnectObservableContext } from './context';
|
|
264
269
|
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
const Client1 = withConnector(({ data }) => ( <div>Year: { data.year }</div> ));
|
|
268
|
-
|
|
269
|
-
const Client2 = withConnector(({ data, setState, resetState }) => {
|
|
270
|
+
export const YearText = ({ data }) => ( <div>Year: { data.year }</div> );
|
|
271
|
+
export const YearInput = ({ data, setState, resetState }) => {
|
|
270
272
|
const onChange = useCallback( e => setState({
|
|
271
273
|
a: { b: { x: { y: { z: { 0: e.target.value } } } } }
|
|
272
274
|
}), [ setState ]);
|
|
@@ -274,7 +276,11 @@ const Client2 = withConnector(({ data, setState, resetState }) => {
|
|
|
274
276
|
data.year > 2049 && resetState([ 'a.b.c' ]);
|
|
275
277
|
}, [ data.year ]);
|
|
276
278
|
return ( <div>Year: <input type="number" onChange={ onChange } /> );
|
|
277
|
-
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const withConnector = connectObservableContext({ year: 'a.b.x.y.z[0]' });
|
|
282
|
+
const Client1 = withConnector( YearText );
|
|
283
|
+
const Client2 = withConnector( YearInput );
|
|
278
284
|
|
|
279
285
|
const Ui = () => (
|
|
280
286
|
<div>
|
|
@@ -285,11 +291,9 @@ const Ui = () => (
|
|
|
285
291
|
|
|
286
292
|
export default Ui;
|
|
287
293
|
```
|
|
288
|
-
```
|
|
289
|
-
/************************************************/
|
|
290
|
-
/* ui.js: using the `useContext` hook method. */
|
|
291
|
-
/************************************************/
|
|
292
294
|
|
|
295
|
+
<i><b><u>ui.js</u></b> (useContext with memo method)</i>
|
|
296
|
+
```
|
|
293
297
|
import React, { memo, useCallback, useEffect } from 'react';
|
|
294
298
|
import ObservableContext, { useObservableContext } from './context';
|
|
295
299
|
|
|
@@ -321,51 +325,67 @@ const Ui = () => (
|
|
|
321
325
|
export default Ui;
|
|
322
326
|
```
|
|
323
327
|
|
|
324
|
-
|
|
328
|
+
<i id="provider-usage"><b><u>provider.js</u></b></i>
|
|
329
|
+
```
|
|
330
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
331
|
+
import ObservableContext from './context';
|
|
332
|
+
import Ui from './ui';
|
|
333
|
+
|
|
334
|
+
const initialState = { a: { b: { c: 25, x: { y: { z: [ 2022 ] } } } } };
|
|
335
|
+
|
|
336
|
+
const createStorageStub = data => ({
|
|
337
|
+
clone( data ) { return <your clone function>( data ) },
|
|
338
|
+
data,
|
|
339
|
+
getItem( key ) { return this.data },
|
|
340
|
+
removeItem( key ) {},
|
|
341
|
+
setItem( key, data ) {}
|
|
342
|
+
});
|
|
325
343
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
344
|
+
const updateHooks = {
|
|
345
|
+
resetState: ( ...args ) => {
|
|
346
|
+
console.log( 'resetting state with >>>> ', JSON.stringify( args ) );
|
|
347
|
+
return true;
|
|
348
|
+
},
|
|
349
|
+
setState: ( ...args ) => {
|
|
350
|
+
console.log( 'merging following into state >>>> ', JSON.stringify( args ) );
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
};
|
|
329
354
|
|
|
330
|
-
|
|
355
|
+
const Provider = ({ c = initialState.c }) => {
|
|
331
356
|
|
|
332
|
-
const storage = {
|
|
333
|
-
clone: data => ({ ...data }),
|
|
334
|
-
getItem: key => initialState,
|
|
335
|
-
removeItem ( key ) {},
|
|
336
|
-
setItem ( key, data ) {}
|
|
337
|
-
};
|
|
357
|
+
const storage = useMemo(() => createStorageStub({ ...initialsState, c }), []);
|
|
338
358
|
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
console.log( 'merging following into state >>>> ', JSON.stringify( args ) );
|
|
346
|
-
return true;
|
|
347
|
-
}
|
|
348
|
-
};
|
|
359
|
+
const [ state, setState ] = useState(() => storage.getItem());
|
|
360
|
+
|
|
361
|
+
useEffect(() => {
|
|
362
|
+
setState({ c }); // use this (similar to `store.setState`) to update only the changed slice of the context internal state.
|
|
363
|
+
// Do not do this: `setState({ ...state, c });` // it will override the context internal state.
|
|
364
|
+
}, [ c ]);
|
|
349
365
|
|
|
350
|
-
|
|
366
|
+
return (
|
|
351
367
|
<ObservableContext.Provider
|
|
352
368
|
prehooks={ updateHooks }
|
|
353
369
|
storage={ storage }
|
|
354
|
-
value={
|
|
370
|
+
value={ state }
|
|
355
371
|
>
|
|
356
372
|
<Client />
|
|
357
373
|
</ObservableContext.Provider>
|
|
358
374
|
);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
375
|
+
};
|
|
376
|
+
Provider.displayName = 'Provider';
|
|
377
|
+
|
|
378
|
+
export default Provider;
|
|
379
|
+
```
|
|
362
380
|
|
|
363
|
-
|
|
381
|
+
<i><b><u>index.js</u></b></i>
|
|
382
|
+
```
|
|
383
|
+
import React from 'react';
|
|
384
|
+
import ReactDOM from 'react-dom';
|
|
385
|
+
import Provider from './provider';
|
|
364
386
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
import Provider from './provider';
|
|
368
|
-
ReactDOM.render(<Provider />, document.getElementById('root'));
|
|
387
|
+
ReactDOM.render( <Provider />, document.getElementById( 'root' ) );
|
|
388
|
+
```
|
|
369
389
|
|
|
370
390
|
<h1 id="changes">What's Changed?</h1>
|
|
371
391
|
<table>
|
package/package.json
CHANGED