@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.
Files changed (53) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +39 -4
  3. package/build/components/use-select/index.js +113 -0
  4. package/build/components/use-select/index.js.map +1 -1
  5. package/build/components/with-registry/index.js.map +1 -1
  6. package/build/controls.js.map +1 -1
  7. package/build/index.js +22 -3
  8. package/build/index.js.map +1 -1
  9. package/build/plugins/persistence/index.js.map +1 -1
  10. package/build/redux-store/index.js +52 -5
  11. package/build/redux-store/index.js.map +1 -1
  12. package/build/redux-store/metadata/utils.js.map +1 -1
  13. package/build/registry.js +31 -4
  14. package/build/registry.js.map +1 -1
  15. package/build/resolvers-cache-middleware.js.map +1 -1
  16. package/build/store/index.js.map +1 -1
  17. package/build-module/components/use-select/index.js +111 -0
  18. package/build-module/components/use-select/index.js.map +1 -1
  19. package/build-module/components/with-registry/index.js.map +1 -1
  20. package/build-module/controls.js.map +1 -1
  21. package/build-module/index.js +13 -1
  22. package/build-module/index.js.map +1 -1
  23. package/build-module/plugins/persistence/index.js.map +1 -1
  24. package/build-module/redux-store/index.js +52 -5
  25. package/build-module/redux-store/index.js.map +1 -1
  26. package/build-module/redux-store/metadata/utils.js.map +1 -1
  27. package/build-module/registry.js +31 -4
  28. package/build-module/registry.js.map +1 -1
  29. package/build-module/resolvers-cache-middleware.js.map +1 -1
  30. package/build-module/store/index.js.map +1 -1
  31. package/build-types/redux-store/metadata/utils.d.ts.map +1 -1
  32. package/build-types/types.d.ts.map +1 -1
  33. package/package.json +8 -8
  34. package/src/components/use-select/index.js +127 -0
  35. package/src/components/use-select/test/index.js +71 -0
  36. package/src/components/use-select/test/suspense.js +164 -0
  37. package/src/components/with-dispatch/test/index.js +4 -5
  38. package/src/components/with-registry/index.js +8 -7
  39. package/src/components/with-select/test/index.js +12 -18
  40. package/src/controls.js +16 -11
  41. package/src/index.js +16 -1
  42. package/src/plugins/persistence/index.js +2 -4
  43. package/src/redux-store/index.js +51 -5
  44. package/src/redux-store/metadata/test/utils.js +1 -2
  45. package/src/redux-store/metadata/utils.ts +24 -24
  46. package/src/redux-store/test/index.js +4 -3
  47. package/src/registry.js +30 -4
  48. package/src/resolvers-cache-middleware.js +33 -34
  49. package/src/store/index.js +10 -6
  50. package/src/test/registry.js +6 -7
  51. package/src/types.ts +4 -5
  52. package/tsconfig.json +2 -2
  53. package/tsconfig.tsbuildinfo +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/data",
3
- "version": "6.8.0",
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.6.0",
32
- "@wordpress/deprecated": "^3.8.0",
33
- "@wordpress/element": "^4.6.0",
34
- "@wordpress/is-shallow-equal": "^4.8.0",
35
- "@wordpress/priority-queue": "^2.8.0",
36
- "@wordpress/redux-routine": "^4.8.0",
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": "4631d515033397fcfeda77e5755960253caef9bf"
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 = testInstance.findByType( 'button' )
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 = testRenderer.root.findByType(
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
- <RegistryConsumer>
22
- { ( registry ) => (
23
- <OriginalComponent { ...props } registry={ registry } />
24
- ) }
25
- </RegistryConsumer>
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 = withSelect( mapSelectToProps )(
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 = withSelect( mapSelectToProps )(
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 = withSelect( mapSelectToProps )(
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 = withSelect( mapSelectToProps )(
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 = withSelect( mapSelectToProps )(
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 = withSelect( mapSelectToProps )(
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 ) => ( { storeKey, selectorName, args } ) =>
122
- registry.select( storeKey )[ selectorName ]( ...args )
121
+ ( registry ) =>
122
+ ( { storeKey, selectorName, args } ) =>
123
+ registry.select( storeKey )[ selectorName ]( ...args )
123
124
  ),
124
125
  [ RESOLVE_SELECT ]: createRegistryControl(
125
- ( registry ) => ( { storeKey, selectorName, args } ) => {
126
- const method = registry.select( storeKey )[ selectorName ]
127
- .hasResolver
128
- ? 'resolveSelect'
129
- : 'select';
130
- return registry[ method ]( storeKey )[ selectorName ]( ...args );
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 ) => ( { storeKey, actionName, args } ) =>
135
- registry.dispatch( storeKey )[ actionName ]( ...args )
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 { default as useSelect } from './components/use-select';
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
- storage = DEFAULT_STORAGE,
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