@wordpress/data 6.8.0 → 6.11.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/CHANGELOG.md +6 -0
- package/README.md +39 -4
- package/build/components/use-select/index.js +113 -0
- package/build/components/use-select/index.js.map +1 -1
- package/build/components/with-registry/index.js.map +1 -1
- package/build/controls.js.map +1 -1
- package/build/index.js +22 -3
- package/build/index.js.map +1 -1
- package/build/plugins/persistence/index.js.map +1 -1
- package/build/redux-store/index.js +52 -5
- package/build/redux-store/index.js.map +1 -1
- package/build/redux-store/metadata/utils.js.map +1 -1
- package/build/registry.js +31 -4
- package/build/registry.js.map +1 -1
- package/build/resolvers-cache-middleware.js.map +1 -1
- package/build/store/index.js.map +1 -1
- package/build-module/components/use-select/index.js +111 -0
- package/build-module/components/use-select/index.js.map +1 -1
- package/build-module/components/with-registry/index.js.map +1 -1
- package/build-module/controls.js.map +1 -1
- package/build-module/index.js +13 -1
- package/build-module/index.js.map +1 -1
- package/build-module/plugins/persistence/index.js.map +1 -1
- package/build-module/redux-store/index.js +52 -5
- package/build-module/redux-store/index.js.map +1 -1
- package/build-module/redux-store/metadata/utils.js.map +1 -1
- package/build-module/registry.js +31 -4
- package/build-module/registry.js.map +1 -1
- package/build-module/resolvers-cache-middleware.js.map +1 -1
- package/build-module/store/index.js.map +1 -1
- package/build-types/redux-store/metadata/utils.d.ts.map +1 -1
- package/build-types/types.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/components/use-select/index.js +127 -0
- package/src/components/use-select/test/index.js +71 -0
- package/src/components/use-select/test/suspense.js +164 -0
- package/src/components/with-dispatch/test/index.js +4 -5
- package/src/components/with-registry/index.js +8 -7
- package/src/components/with-select/test/index.js +12 -18
- package/src/controls.js +16 -11
- package/src/index.js +16 -1
- package/src/plugins/persistence/index.js +2 -4
- package/src/redux-store/index.js +51 -5
- package/src/redux-store/metadata/test/utils.js +1 -2
- package/src/redux-store/metadata/utils.ts +24 -24
- package/src/redux-store/test/index.js +4 -3
- package/src/registry.js +30 -4
- package/src/resolvers-cache-middleware.js +33 -34
- package/src/store/index.js +10 -6
- package/src/test/registry.js +6 -7
- package/src/types.ts +4 -5
- package/tsconfig.json +2 -2
- package/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/data",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.11.0",
|
|
4
4
|
"description": "Data module for WordPress.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@babel/runtime": "^7.16.0",
|
|
31
|
-
"@wordpress/compose": "^5.
|
|
32
|
-
"@wordpress/deprecated": "^3.
|
|
33
|
-
"@wordpress/element": "^4.
|
|
34
|
-
"@wordpress/is-shallow-equal": "^4.
|
|
35
|
-
"@wordpress/priority-queue": "^2.
|
|
36
|
-
"@wordpress/redux-routine": "^4.
|
|
31
|
+
"@wordpress/compose": "^5.9.0",
|
|
32
|
+
"@wordpress/deprecated": "^3.11.0",
|
|
33
|
+
"@wordpress/element": "^4.9.0",
|
|
34
|
+
"@wordpress/is-shallow-equal": "^4.11.0",
|
|
35
|
+
"@wordpress/priority-queue": "^2.11.0",
|
|
36
|
+
"@wordpress/redux-routine": "^4.11.0",
|
|
37
37
|
"equivalent-key-map": "^0.2.2",
|
|
38
38
|
"is-promise": "^4.0.0",
|
|
39
39
|
"lodash": "^4.17.21",
|
|
@@ -47,5 +47,5 @@
|
|
|
47
47
|
"publishConfig": {
|
|
48
48
|
"access": "public"
|
|
49
49
|
},
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "48d5f37dfb52d2e77c8eeb662f9874cf141b8c6b"
|
|
51
51
|
}
|
|
@@ -241,3 +241,130 @@ export default function useSelect( mapSelect, deps ) {
|
|
|
241
241
|
|
|
242
242
|
return hasMappingFunction ? mapOutput : registry.select( mapSelect );
|
|
243
243
|
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* A variant of the `useSelect` hook that has the same API, but will throw a
|
|
247
|
+
* suspense Promise if any of the called selectors is in an unresolved state.
|
|
248
|
+
*
|
|
249
|
+
* @param {Function} mapSelect Function called on every state change. The
|
|
250
|
+
* returned value is exposed to the component
|
|
251
|
+
* using this hook. The function receives the
|
|
252
|
+
* `registry.suspendSelect` method as the first
|
|
253
|
+
* argument and the `registry` as the second one.
|
|
254
|
+
* @param {Array} deps A dependency array used to memoize the `mapSelect`
|
|
255
|
+
* so that the same `mapSelect` is invoked on every
|
|
256
|
+
* state change unless the dependencies change.
|
|
257
|
+
*
|
|
258
|
+
* @return {Object} Data object returned by the `mapSelect` function.
|
|
259
|
+
*/
|
|
260
|
+
export function useSuspenseSelect( mapSelect, deps ) {
|
|
261
|
+
const _mapSelect = useCallback( mapSelect, deps );
|
|
262
|
+
|
|
263
|
+
const registry = useRegistry();
|
|
264
|
+
const isAsync = useAsyncMode();
|
|
265
|
+
|
|
266
|
+
const latestRegistry = useRef( registry );
|
|
267
|
+
const latestMapSelect = useRef();
|
|
268
|
+
const latestIsAsync = useRef( isAsync );
|
|
269
|
+
const latestMapOutput = useRef();
|
|
270
|
+
const latestMapOutputError = useRef();
|
|
271
|
+
|
|
272
|
+
// Keep track of the stores being selected in the `mapSelect` function,
|
|
273
|
+
// and only subscribe to those stores later.
|
|
274
|
+
const listeningStores = useRef( [] );
|
|
275
|
+
const wrapSelect = useCallback(
|
|
276
|
+
( callback ) =>
|
|
277
|
+
registry.__unstableMarkListeningStores(
|
|
278
|
+
() => callback( registry.suspendSelect, registry ),
|
|
279
|
+
listeningStores
|
|
280
|
+
),
|
|
281
|
+
[ registry ]
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// Generate a "flag" for used in the effect dependency array.
|
|
285
|
+
// It's different than just using `mapSelect` since deps could be undefined,
|
|
286
|
+
// in that case, we would still want to memoize it.
|
|
287
|
+
const depsChangedFlag = useMemo( () => ( {} ), deps || [] );
|
|
288
|
+
|
|
289
|
+
let mapOutput = latestMapOutput.current;
|
|
290
|
+
let mapOutputError = latestMapOutputError.current;
|
|
291
|
+
|
|
292
|
+
const hasReplacedRegistry = latestRegistry.current !== registry;
|
|
293
|
+
const hasReplacedMapSelect = latestMapSelect.current !== _mapSelect;
|
|
294
|
+
const hasLeftAsyncMode = latestIsAsync.current && ! isAsync;
|
|
295
|
+
|
|
296
|
+
if ( hasReplacedRegistry || hasReplacedMapSelect || hasLeftAsyncMode ) {
|
|
297
|
+
try {
|
|
298
|
+
mapOutput = wrapSelect( _mapSelect );
|
|
299
|
+
} catch ( error ) {
|
|
300
|
+
mapOutputError = error;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
useIsomorphicLayoutEffect( () => {
|
|
305
|
+
latestRegistry.current = registry;
|
|
306
|
+
latestMapSelect.current = _mapSelect;
|
|
307
|
+
latestIsAsync.current = isAsync;
|
|
308
|
+
latestMapOutput.current = mapOutput;
|
|
309
|
+
latestMapOutputError.current = mapOutputError;
|
|
310
|
+
} );
|
|
311
|
+
|
|
312
|
+
// React can sometimes clear the `useMemo` cache.
|
|
313
|
+
// We use the cache-stable `useMemoOne` to avoid
|
|
314
|
+
// losing queues.
|
|
315
|
+
const queueContext = useMemoOne( () => ( { queue: true } ), [ registry ] );
|
|
316
|
+
const [ , forceRender ] = useReducer( ( s ) => s + 1, 0 );
|
|
317
|
+
const isMounted = useRef( false );
|
|
318
|
+
|
|
319
|
+
useIsomorphicLayoutEffect( () => {
|
|
320
|
+
const onStoreChange = () => {
|
|
321
|
+
try {
|
|
322
|
+
const newMapOutput = wrapSelect( latestMapSelect.current );
|
|
323
|
+
|
|
324
|
+
if ( isShallowEqual( latestMapOutput.current, newMapOutput ) ) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
latestMapOutput.current = newMapOutput;
|
|
328
|
+
} catch ( error ) {
|
|
329
|
+
latestMapOutputError.current = error;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
forceRender();
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const onChange = () => {
|
|
336
|
+
if ( ! isMounted.current ) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if ( latestIsAsync.current ) {
|
|
341
|
+
renderQueue.add( queueContext, onStoreChange );
|
|
342
|
+
} else {
|
|
343
|
+
onStoreChange();
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// catch any possible state changes during mount before the subscription
|
|
348
|
+
// could be set.
|
|
349
|
+
onStoreChange();
|
|
350
|
+
|
|
351
|
+
const unsubscribers = listeningStores.current.map( ( storeName ) =>
|
|
352
|
+
registry.__unstableSubscribeStore( storeName, onChange )
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
isMounted.current = true;
|
|
356
|
+
|
|
357
|
+
return () => {
|
|
358
|
+
// The return value of the subscribe function could be undefined if the store is a custom generic store.
|
|
359
|
+
unsubscribers.forEach( ( unsubscribe ) => unsubscribe?.() );
|
|
360
|
+
renderQueue.cancel( queueContext );
|
|
361
|
+
isMounted.current = false;
|
|
362
|
+
};
|
|
363
|
+
}, [ registry, wrapSelect, depsChangedFlag ] );
|
|
364
|
+
|
|
365
|
+
if ( mapOutputError ) {
|
|
366
|
+
throw mapOutputError;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return mapOutput;
|
|
370
|
+
}
|
|
@@ -1025,4 +1025,75 @@ describe( 'useSelect', () => {
|
|
|
1025
1025
|
expect( TestComponent ).toHaveBeenCalledTimes( 2 );
|
|
1026
1026
|
} );
|
|
1027
1027
|
} );
|
|
1028
|
+
|
|
1029
|
+
describe( 'usage without dependencies array', () => {
|
|
1030
|
+
function registerStore( name, initial ) {
|
|
1031
|
+
registry.registerStore( name, {
|
|
1032
|
+
reducer: ( s = initial, a ) => ( a.type === 'inc' ? s + 1 : s ),
|
|
1033
|
+
actions: { inc: () => ( { type: 'inc' } ) },
|
|
1034
|
+
selectors: { get: ( s ) => s },
|
|
1035
|
+
} );
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
it( 'does not memoize the callback when there are no deps', () => {
|
|
1039
|
+
registerStore( 'store', 1 );
|
|
1040
|
+
|
|
1041
|
+
const Status = ( { multiple } ) => {
|
|
1042
|
+
const count = useSelect(
|
|
1043
|
+
( select ) => select( 'store' ).get() * multiple
|
|
1044
|
+
);
|
|
1045
|
+
return <div role="status">{ count }</div>;
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
const App = ( { multiple } ) => (
|
|
1049
|
+
<RegistryProvider value={ registry }>
|
|
1050
|
+
<Status multiple={ multiple } />
|
|
1051
|
+
</RegistryProvider>
|
|
1052
|
+
);
|
|
1053
|
+
|
|
1054
|
+
const rendered = render( <App multiple={ 1 } /> );
|
|
1055
|
+
expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 1 );
|
|
1056
|
+
|
|
1057
|
+
// Check that the most recent value of `multiple` is used to render:
|
|
1058
|
+
// the old callback wasn't memoized and there is no stale closure problem.
|
|
1059
|
+
rendered.rerender( <App multiple={ 2 } /> );
|
|
1060
|
+
expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 2 );
|
|
1061
|
+
} );
|
|
1062
|
+
|
|
1063
|
+
it( 'subscribes only stores used by the initial callback', () => {
|
|
1064
|
+
registerStore( 'counter-1', 1 );
|
|
1065
|
+
registerStore( 'counter-2', 10 );
|
|
1066
|
+
|
|
1067
|
+
const Status = ( { store } ) => {
|
|
1068
|
+
const count = useSelect( ( select ) => select( store ).get() );
|
|
1069
|
+
return <div role="status">{ count }</div>;
|
|
1070
|
+
};
|
|
1071
|
+
|
|
1072
|
+
const App = ( { store } ) => (
|
|
1073
|
+
<RegistryProvider value={ registry }>
|
|
1074
|
+
<Status store={ store } />
|
|
1075
|
+
</RegistryProvider>
|
|
1076
|
+
);
|
|
1077
|
+
|
|
1078
|
+
// initial render with counter-1
|
|
1079
|
+
const rendered = render( <App store="counter-1" /> );
|
|
1080
|
+
expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 1 );
|
|
1081
|
+
|
|
1082
|
+
// update from counter-1
|
|
1083
|
+
act( () => {
|
|
1084
|
+
registry.dispatch( 'counter-1' ).inc();
|
|
1085
|
+
} );
|
|
1086
|
+
expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 2 );
|
|
1087
|
+
|
|
1088
|
+
// rerender with counter-2
|
|
1089
|
+
rendered.rerender( <App store="counter-2" /> );
|
|
1090
|
+
expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 10 );
|
|
1091
|
+
|
|
1092
|
+
// update from counter-2 is ignored because component is subcribed only to counter-1
|
|
1093
|
+
act( () => {
|
|
1094
|
+
registry.dispatch( 'counter-2' ).inc();
|
|
1095
|
+
} );
|
|
1096
|
+
expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 10 );
|
|
1097
|
+
} );
|
|
1098
|
+
} );
|
|
1028
1099
|
} );
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { render, waitFor } from '@testing-library/react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WordPress dependencies
|
|
8
|
+
*/
|
|
9
|
+
import {
|
|
10
|
+
createRegistry,
|
|
11
|
+
createReduxStore,
|
|
12
|
+
useSuspenseSelect,
|
|
13
|
+
RegistryProvider,
|
|
14
|
+
} from '@wordpress/data';
|
|
15
|
+
import { Component, Suspense } from '@wordpress/element';
|
|
16
|
+
|
|
17
|
+
jest.useRealTimers();
|
|
18
|
+
|
|
19
|
+
function createRegistryWithStore() {
|
|
20
|
+
const initialState = {
|
|
21
|
+
prefix: 'pre-',
|
|
22
|
+
token: null,
|
|
23
|
+
data: null,
|
|
24
|
+
fails: true,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const reducer = ( state = initialState, action ) => {
|
|
28
|
+
switch ( action.type ) {
|
|
29
|
+
case 'RECEIVE_TOKEN':
|
|
30
|
+
return { ...state, token: action.token };
|
|
31
|
+
case 'RECEIVE_DATA':
|
|
32
|
+
return { ...state, data: action.data };
|
|
33
|
+
default:
|
|
34
|
+
return state;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const selectors = {
|
|
39
|
+
getPrefix: ( state ) => state.prefix,
|
|
40
|
+
getToken: ( state ) => state.token,
|
|
41
|
+
getData: ( state, token ) => {
|
|
42
|
+
if ( ! token ) {
|
|
43
|
+
throw 'missing token in selector';
|
|
44
|
+
}
|
|
45
|
+
return state.data;
|
|
46
|
+
},
|
|
47
|
+
getThatFails: ( state ) => state.fails,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const sleep = ( ms ) => new Promise( ( r ) => setTimeout( () => r(), ms ) );
|
|
51
|
+
|
|
52
|
+
const resolvers = {
|
|
53
|
+
getToken:
|
|
54
|
+
() =>
|
|
55
|
+
async ( { dispatch } ) => {
|
|
56
|
+
await sleep( 10 );
|
|
57
|
+
dispatch( { type: 'RECEIVE_TOKEN', token: 'token' } );
|
|
58
|
+
},
|
|
59
|
+
getData:
|
|
60
|
+
( token ) =>
|
|
61
|
+
async ( { dispatch } ) => {
|
|
62
|
+
await sleep( 10 );
|
|
63
|
+
if ( ! token ) {
|
|
64
|
+
throw 'missing token in resolver';
|
|
65
|
+
}
|
|
66
|
+
dispatch( { type: 'RECEIVE_DATA', data: 'therealdata' } );
|
|
67
|
+
},
|
|
68
|
+
getThatFails: () => async () => {
|
|
69
|
+
await sleep( 10 );
|
|
70
|
+
throw 'resolution failed';
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const store = createReduxStore( 'test', {
|
|
75
|
+
reducer,
|
|
76
|
+
selectors,
|
|
77
|
+
resolvers,
|
|
78
|
+
} );
|
|
79
|
+
|
|
80
|
+
const registry = createRegistry();
|
|
81
|
+
registry.register( store );
|
|
82
|
+
|
|
83
|
+
return { registry, store };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
describe( 'useSuspenseSelect', () => {
|
|
87
|
+
it( 'renders after suspending a few times', async () => {
|
|
88
|
+
const { registry, store } = createRegistryWithStore();
|
|
89
|
+
let attempts = 0;
|
|
90
|
+
let renders = 0;
|
|
91
|
+
|
|
92
|
+
const UI = () => {
|
|
93
|
+
attempts++;
|
|
94
|
+
const { result } = useSuspenseSelect( ( select ) => {
|
|
95
|
+
const prefix = select( store ).getPrefix();
|
|
96
|
+
const token = select( store ).getToken();
|
|
97
|
+
const data = select( store ).getData( token );
|
|
98
|
+
return { result: prefix + data };
|
|
99
|
+
}, [] );
|
|
100
|
+
renders++;
|
|
101
|
+
return <div aria-label="loaded">{ result }</div>;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const App = () => (
|
|
105
|
+
<RegistryProvider value={ registry }>
|
|
106
|
+
<Suspense fallback="loading">
|
|
107
|
+
<UI />
|
|
108
|
+
</Suspense>
|
|
109
|
+
</RegistryProvider>
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const rendered = render( <App /> );
|
|
113
|
+
await waitFor( () => rendered.getByLabelText( 'loaded' ) );
|
|
114
|
+
|
|
115
|
+
// Verify there were 3 attempts to render. Suspended twice because of
|
|
116
|
+
// `getToken` and `getData` selectors not being resolved, and then finally
|
|
117
|
+
// rendered after all data got loaded.
|
|
118
|
+
expect( attempts ).toBe( 3 );
|
|
119
|
+
expect( renders ).toBe( 1 );
|
|
120
|
+
} );
|
|
121
|
+
|
|
122
|
+
it( 'shows error when resolution fails', async () => {
|
|
123
|
+
const { registry, store } = createRegistryWithStore();
|
|
124
|
+
|
|
125
|
+
const UI = () => {
|
|
126
|
+
const { token } = useSuspenseSelect( ( select ) => {
|
|
127
|
+
// Call a selector whose resolution fails. The `useSuspenseSelect`
|
|
128
|
+
// is then supposed to throw the resolution error.
|
|
129
|
+
return { token: select( store ).getThatFails() };
|
|
130
|
+
}, [] );
|
|
131
|
+
return <div aria-label="loaded">{ token }</div>;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
class Error extends Component {
|
|
135
|
+
state = { error: null };
|
|
136
|
+
|
|
137
|
+
static getDerivedStateFromError( error ) {
|
|
138
|
+
return { error };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
render() {
|
|
142
|
+
if ( this.state.error ) {
|
|
143
|
+
return <div aria-label="error">{ this.state.error }</div>;
|
|
144
|
+
}
|
|
145
|
+
return this.props.children;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const App = () => (
|
|
150
|
+
<RegistryProvider value={ registry }>
|
|
151
|
+
<Error>
|
|
152
|
+
<Suspense fallback="loading">
|
|
153
|
+
<UI />
|
|
154
|
+
</Suspense>
|
|
155
|
+
</Error>
|
|
156
|
+
</RegistryProvider>
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const rendered = render( <App /> );
|
|
160
|
+
const label = await waitFor( () => rendered.getByLabelText( 'error' ) );
|
|
161
|
+
expect( label.textContent ).toBe( 'resolution failed' );
|
|
162
|
+
expect( console ).toHaveErrored();
|
|
163
|
+
} );
|
|
164
|
+
} );
|
|
@@ -57,8 +57,8 @@ describe( 'withDispatch', () => {
|
|
|
57
57
|
} );
|
|
58
58
|
const testInstance = testRenderer.root;
|
|
59
59
|
|
|
60
|
-
const incrementBeforeSetProps =
|
|
61
|
-
.props.onClick;
|
|
60
|
+
const incrementBeforeSetProps =
|
|
61
|
+
testInstance.findByType( 'button' ).props.onClick;
|
|
62
62
|
|
|
63
63
|
// Verify that dispatch respects props at the time of being invoked by
|
|
64
64
|
// changing props after the initial mount.
|
|
@@ -194,9 +194,8 @@ describe( 'withDispatch', () => {
|
|
|
194
194
|
);
|
|
195
195
|
} );
|
|
196
196
|
|
|
197
|
-
const counterUpdateHandler =
|
|
198
|
-
'button'
|
|
199
|
-
).props.onClick;
|
|
197
|
+
const counterUpdateHandler =
|
|
198
|
+
testRenderer.root.findByType( 'button' ).props.onClick;
|
|
200
199
|
|
|
201
200
|
act( () => {
|
|
202
201
|
counterUpdateHandler();
|
|
@@ -17,13 +17,14 @@ import { RegistryConsumer } from '../registry-provider';
|
|
|
17
17
|
* @return {WPComponent} Enhanced component.
|
|
18
18
|
*/
|
|
19
19
|
const withRegistry = createHigherOrderComponent(
|
|
20
|
-
( OriginalComponent ) => ( props ) =>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
( OriginalComponent ) => ( props ) =>
|
|
21
|
+
(
|
|
22
|
+
<RegistryConsumer>
|
|
23
|
+
{ ( registry ) => (
|
|
24
|
+
<OriginalComponent { ...props } registry={ registry } />
|
|
25
|
+
) }
|
|
26
|
+
</RegistryConsumer>
|
|
27
|
+
),
|
|
27
28
|
'withRegistry'
|
|
28
29
|
);
|
|
29
30
|
|
|
@@ -50,9 +50,8 @@ describe( 'withSelect', () => {
|
|
|
50
50
|
.fn()
|
|
51
51
|
.mockImplementation( ( props ) => <div>{ props.data }</div> );
|
|
52
52
|
|
|
53
|
-
const DataBoundComponent =
|
|
54
|
-
OriginalComponent
|
|
55
|
-
);
|
|
53
|
+
const DataBoundComponent =
|
|
54
|
+
withSelect( mapSelectToProps )( OriginalComponent );
|
|
56
55
|
let testRenderer;
|
|
57
56
|
act( () => {
|
|
58
57
|
testRenderer = TestRenderer.create(
|
|
@@ -268,9 +267,8 @@ describe( 'withSelect', () => {
|
|
|
268
267
|
.fn()
|
|
269
268
|
.mockImplementation( ( props ) => <div>{ props.count }</div> );
|
|
270
269
|
|
|
271
|
-
const DataBoundComponent =
|
|
272
|
-
OriginalComponent
|
|
273
|
-
);
|
|
270
|
+
const DataBoundComponent =
|
|
271
|
+
withSelect( mapSelectToProps )( OriginalComponent );
|
|
274
272
|
|
|
275
273
|
let testRenderer;
|
|
276
274
|
act( () => {
|
|
@@ -367,9 +365,8 @@ describe( 'withSelect', () => {
|
|
|
367
365
|
|
|
368
366
|
const OriginalComponent = jest.fn().mockImplementation( () => <div /> );
|
|
369
367
|
|
|
370
|
-
const DataBoundComponent =
|
|
371
|
-
OriginalComponent
|
|
372
|
-
);
|
|
368
|
+
const DataBoundComponent =
|
|
369
|
+
withSelect( mapSelectToProps )( OriginalComponent );
|
|
373
370
|
|
|
374
371
|
act( () => {
|
|
375
372
|
TestRenderer.create(
|
|
@@ -492,9 +489,8 @@ describe( 'withSelect', () => {
|
|
|
492
489
|
<div>{ JSON.stringify( props ) }</div>
|
|
493
490
|
) );
|
|
494
491
|
|
|
495
|
-
const DataBoundComponent =
|
|
496
|
-
OriginalComponent
|
|
497
|
-
);
|
|
492
|
+
const DataBoundComponent =
|
|
493
|
+
withSelect( mapSelectToProps )( OriginalComponent );
|
|
498
494
|
|
|
499
495
|
let testRenderer;
|
|
500
496
|
act( () => {
|
|
@@ -561,9 +557,8 @@ describe( 'withSelect', () => {
|
|
|
561
557
|
<div>{ props.count || 'Unknown' }</div>
|
|
562
558
|
) );
|
|
563
559
|
|
|
564
|
-
const DataBoundComponent =
|
|
565
|
-
OriginalComponent
|
|
566
|
-
);
|
|
560
|
+
const DataBoundComponent =
|
|
561
|
+
withSelect( mapSelectToProps )( OriginalComponent );
|
|
567
562
|
|
|
568
563
|
let testRenderer;
|
|
569
564
|
act( () => {
|
|
@@ -691,9 +686,8 @@ describe( 'withSelect', () => {
|
|
|
691
686
|
.fn()
|
|
692
687
|
.mockImplementation( ( props ) => <div>{ props.value }</div> );
|
|
693
688
|
|
|
694
|
-
const DataBoundComponent =
|
|
695
|
-
OriginalComponent
|
|
696
|
-
);
|
|
689
|
+
const DataBoundComponent =
|
|
690
|
+
withSelect( mapSelectToProps )( OriginalComponent );
|
|
697
691
|
|
|
698
692
|
let testRenderer;
|
|
699
693
|
act( () => {
|
package/src/controls.js
CHANGED
|
@@ -118,20 +118,25 @@ export const controls = { select, resolveSelect, dispatch };
|
|
|
118
118
|
|
|
119
119
|
export const builtinControls = {
|
|
120
120
|
[ SELECT ]: createRegistryControl(
|
|
121
|
-
( registry ) =>
|
|
122
|
-
|
|
121
|
+
( registry ) =>
|
|
122
|
+
( { storeKey, selectorName, args } ) =>
|
|
123
|
+
registry.select( storeKey )[ selectorName ]( ...args )
|
|
123
124
|
),
|
|
124
125
|
[ RESOLVE_SELECT ]: createRegistryControl(
|
|
125
|
-
( registry ) =>
|
|
126
|
-
|
|
127
|
-
.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
( registry ) =>
|
|
127
|
+
( { storeKey, selectorName, args } ) => {
|
|
128
|
+
const method = registry.select( storeKey )[ selectorName ]
|
|
129
|
+
.hasResolver
|
|
130
|
+
? 'resolveSelect'
|
|
131
|
+
: 'select';
|
|
132
|
+
return registry[ method ]( storeKey )[ selectorName ](
|
|
133
|
+
...args
|
|
134
|
+
);
|
|
135
|
+
}
|
|
132
136
|
),
|
|
133
137
|
[ DISPATCH ]: createRegistryControl(
|
|
134
|
-
( registry ) =>
|
|
135
|
-
|
|
138
|
+
( registry ) =>
|
|
139
|
+
( { storeKey, actionName, args } ) =>
|
|
140
|
+
registry.dispatch( storeKey )[ actionName ]( ...args )
|
|
136
141
|
),
|
|
137
142
|
};
|
package/src/index.js
CHANGED
|
@@ -19,7 +19,10 @@ export {
|
|
|
19
19
|
RegistryConsumer,
|
|
20
20
|
useRegistry,
|
|
21
21
|
} from './components/registry-provider';
|
|
22
|
-
export {
|
|
22
|
+
export {
|
|
23
|
+
default as useSelect,
|
|
24
|
+
useSuspenseSelect,
|
|
25
|
+
} from './components/use-select';
|
|
23
26
|
export { useDispatch } from './components/use-dispatch';
|
|
24
27
|
export { AsyncModeProvider } from './components/async-mode-provider';
|
|
25
28
|
export { createRegistry } from './registry';
|
|
@@ -115,6 +118,18 @@ export const select = defaultRegistry.select;
|
|
|
115
118
|
*/
|
|
116
119
|
export const resolveSelect = defaultRegistry.resolveSelect;
|
|
117
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Given the name of a registered store, returns an object containing the store's
|
|
123
|
+
* selectors pre-bound to state so that you only need to supply additional arguments,
|
|
124
|
+
* and modified so that they throw promises in case the selector is not resolved yet.
|
|
125
|
+
*
|
|
126
|
+
* @param {string|StoreDescriptor} storeNameOrDescriptor Unique namespace identifier for the store
|
|
127
|
+
* or the store descriptor.
|
|
128
|
+
*
|
|
129
|
+
* @return {Object} Object containing the store's suspense-wrapped selectors.
|
|
130
|
+
*/
|
|
131
|
+
export const suspendSelect = defaultRegistry.suspendSelect;
|
|
132
|
+
|
|
118
133
|
/**
|
|
119
134
|
* Given the name of a registered store, returns an object of the store's action creators.
|
|
120
135
|
* Calling an action creator will cause it to be dispatched, updating the state value accordingly.
|
|
@@ -63,10 +63,8 @@ export const withLazySameState = ( reducer ) => ( state, action ) => {
|
|
|
63
63
|
* @return {Object} Persistence interface.
|
|
64
64
|
*/
|
|
65
65
|
export function createPersistenceInterface( options ) {
|
|
66
|
-
const {
|
|
67
|
-
|
|
68
|
-
storageKey = DEFAULT_STORAGE_KEY,
|
|
69
|
-
} = options;
|
|
66
|
+
const { storage = DEFAULT_STORAGE, storageKey = DEFAULT_STORAGE_KEY } =
|
|
67
|
+
options;
|
|
70
68
|
|
|
71
69
|
let data;
|
|
72
70
|
|