@webkrafters/react-observable-context 4.0.0-rc.0 → 4.0.0-rc.2
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 +79 -63
- 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,21 @@ The React-Observable-Context module contains **4** exports namely:
|
|
|
245
249
|
|
|
246
250
|
# Usage
|
|
247
251
|
|
|
248
|
-
|
|
249
|
-
|
|
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;
|
|
252
|
+
<i><b><u>context.js</u></b></i>
|
|
253
|
+
```
|
|
254
|
+
import { createContext } from '@webkrafters/react-observable-context';
|
|
255
255
|
|
|
256
|
-
|
|
256
|
+
export default createContext();
|
|
257
257
|
```
|
|
258
|
-
/********************************************/
|
|
259
|
-
/* ui.js: using the `connect` HOC method. */
|
|
260
|
-
/********************************************/
|
|
261
258
|
|
|
259
|
+
<i><b><u>ui.js</u></b> (connect method)</i>
|
|
260
|
+
```
|
|
262
261
|
import React, { useCallback, useEffect } from 'react';
|
|
263
|
-
import
|
|
264
|
-
|
|
265
|
-
const withConnector = connectObservableContext({ year: 'a.b.x.y.z[0]' });
|
|
266
|
-
|
|
267
|
-
const Client1 = withConnector(({ data }) => ( <div>Year: { data.year }</div> ));
|
|
262
|
+
import { connect } from '@webkrafters/react-observable-context';
|
|
263
|
+
import ObservableContext from './context';
|
|
268
264
|
|
|
269
|
-
const
|
|
265
|
+
export const YearText = ({ data }) => ( <div>Year: { data.year }</div> );
|
|
266
|
+
export const YearInput = ({ data, setState, resetState }) => {
|
|
270
267
|
const onChange = useCallback( e => setState({
|
|
271
268
|
a: { b: { x: { y: { z: { 0: e.target.value } } } } }
|
|
272
269
|
}), [ setState ]);
|
|
@@ -274,7 +271,11 @@ const Client2 = withConnector(({ data, setState, resetState }) => {
|
|
|
274
271
|
data.year > 2049 && resetState([ 'a.b.c' ]);
|
|
275
272
|
}, [ data.year ]);
|
|
276
273
|
return ( <div>Year: <input type="number" onChange={ onChange } /> );
|
|
277
|
-
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const withConnector = connect( ObservablContext, { year: 'a.b.x.y.z[0]' } );
|
|
277
|
+
const Client1 = withConnector( YearText );
|
|
278
|
+
const Client2 = withConnector( YearInput );
|
|
278
279
|
|
|
279
280
|
const Ui = () => (
|
|
280
281
|
<div>
|
|
@@ -285,23 +286,22 @@ const Ui = () => (
|
|
|
285
286
|
|
|
286
287
|
export default Ui;
|
|
287
288
|
```
|
|
288
|
-
```
|
|
289
|
-
/************************************************/
|
|
290
|
-
/* ui.js: using the `useContext` hook method. */
|
|
291
|
-
/************************************************/
|
|
292
289
|
|
|
290
|
+
<i><b><u>ui.js</u></b> (useContext with memo method)</i>
|
|
291
|
+
```
|
|
293
292
|
import React, { memo, useCallback, useEffect } from 'react';
|
|
294
|
-
import
|
|
293
|
+
import { useContext } from '@webkrafters/react-observable-context';
|
|
294
|
+
import ObservableContext from './context';
|
|
295
295
|
|
|
296
296
|
const selectorMap = { year: 'a.b.x.y.z[0]' };
|
|
297
297
|
|
|
298
298
|
const Client1 = memo(() => { // memoize to prevent 'no-change' renders from the parent.
|
|
299
|
-
const { data } =
|
|
299
|
+
const { data } = useContext( ObservableContext, selectorMap );
|
|
300
300
|
return ( <div>Year: { data.year }</div> );
|
|
301
301
|
});
|
|
302
302
|
|
|
303
303
|
const Client2 = memo(() => { // memoize to prevent 'no-change' renders from the parent.
|
|
304
|
-
const { data, setState, resetState } =
|
|
304
|
+
const { data, setState, resetState } = useContext( ObservableContext, selectorMap );
|
|
305
305
|
const onChange = useCallback( e => setState({
|
|
306
306
|
a: { b: { x: { y: { z: { 0: e.target.value } } } } }
|
|
307
307
|
}), [ setState ]);
|
|
@@ -321,51 +321,67 @@ const Ui = () => (
|
|
|
321
321
|
export default Ui;
|
|
322
322
|
```
|
|
323
323
|
|
|
324
|
-
|
|
324
|
+
<i id="provider-usage"><b><u>provider.js</u></b></i>
|
|
325
|
+
```
|
|
326
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
327
|
+
import ObservableContext from './context';
|
|
328
|
+
import Ui from './ui';
|
|
329
|
+
|
|
330
|
+
const initialState = { a: { b: { c: 25, x: { y: { z: [ 2022 ] } } } } };
|
|
331
|
+
|
|
332
|
+
const createStorageStub = data => ({
|
|
333
|
+
clone( data ) { return <your clone function>( data ) },
|
|
334
|
+
data,
|
|
335
|
+
getItem( key ) { return this.data },
|
|
336
|
+
removeItem( key ) {},
|
|
337
|
+
setItem( key, data ) {}
|
|
338
|
+
});
|
|
325
339
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
340
|
+
const updateHooks = {
|
|
341
|
+
resetState: ( ...args ) => {
|
|
342
|
+
console.log( 'resetting state with >>>> ', JSON.stringify( args ) );
|
|
343
|
+
return true;
|
|
344
|
+
},
|
|
345
|
+
setState: ( ...args ) => {
|
|
346
|
+
console.log( 'merging following into state >>>> ', JSON.stringify( args ) );
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
};
|
|
329
350
|
|
|
330
|
-
|
|
351
|
+
const Provider = ({ c = initialState.c }) => {
|
|
331
352
|
|
|
332
|
-
const storage = {
|
|
333
|
-
clone: data => ({ ...data }),
|
|
334
|
-
getItem: key => initialState,
|
|
335
|
-
removeItem ( key ) {},
|
|
336
|
-
setItem ( key, data ) {}
|
|
337
|
-
};
|
|
353
|
+
const storage = useMemo(() => createStorageStub({ ...initialsState, c }), []);
|
|
338
354
|
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
console.log( 'merging following into state >>>> ', JSON.stringify( args ) );
|
|
346
|
-
return true;
|
|
347
|
-
}
|
|
348
|
-
};
|
|
355
|
+
const [ state, setState ] = useState(() => storage.getItem());
|
|
356
|
+
|
|
357
|
+
useEffect(() => {
|
|
358
|
+
setState({ c }); // use this (similar to `store.setState`) to update only the changed slice of the context internal state.
|
|
359
|
+
// Do not do this: `setState({ ...state, c });` // it will override the context internal state.
|
|
360
|
+
}, [ c ]);
|
|
349
361
|
|
|
350
|
-
|
|
362
|
+
return (
|
|
351
363
|
<ObservableContext.Provider
|
|
352
364
|
prehooks={ updateHooks }
|
|
353
365
|
storage={ storage }
|
|
354
|
-
value={
|
|
366
|
+
value={ state }
|
|
355
367
|
>
|
|
356
368
|
<Client />
|
|
357
369
|
</ObservableContext.Provider>
|
|
358
370
|
);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
371
|
+
};
|
|
372
|
+
Provider.displayName = 'Provider';
|
|
373
|
+
|
|
374
|
+
export default Provider;
|
|
375
|
+
```
|
|
362
376
|
|
|
363
|
-
|
|
377
|
+
<i><b><u>index.js</u></b></i>
|
|
378
|
+
```
|
|
379
|
+
import React from 'react';
|
|
380
|
+
import ReactDOM from 'react-dom';
|
|
381
|
+
import Provider from './provider';
|
|
364
382
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
import Provider from './provider';
|
|
368
|
-
ReactDOM.render(<Provider />, document.getElementById('root'));
|
|
383
|
+
ReactDOM.render( <Provider />, document.getElementById( 'root' ) );
|
|
384
|
+
```
|
|
369
385
|
|
|
370
386
|
<h1 id="changes">What's Changed?</h1>
|
|
371
387
|
<table>
|
package/package.json
CHANGED