@wordpress/core-data 4.1.1 → 4.2.0-next.e230fbab09.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 (182) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +2 -10
  3. package/build/actions.js +9 -13
  4. package/build/actions.js.map +1 -1
  5. package/build/batch/create-batch.js +17 -12
  6. package/build/batch/create-batch.js.map +1 -1
  7. package/build/entities.js +1 -1
  8. package/build/entities.js.map +1 -1
  9. package/build/entity-types/attachment.js +6 -0
  10. package/build/entity-types/attachment.js.map +1 -0
  11. package/build/entity-types/base-entity-types.js +47 -0
  12. package/build/entity-types/base-entity-types.js.map +1 -0
  13. package/build/entity-types/comment.js +6 -0
  14. package/build/entity-types/comment.js.map +1 -0
  15. package/build/entity-types/helpers.js +6 -0
  16. package/build/entity-types/helpers.js.map +1 -0
  17. package/build/entity-types/index.js +6 -0
  18. package/build/entity-types/index.js.map +1 -0
  19. package/build/entity-types/menu-location.js +6 -0
  20. package/build/entity-types/menu-location.js.map +1 -0
  21. package/build/entity-types/nav-menu-item.js +6 -0
  22. package/build/entity-types/nav-menu-item.js.map +1 -0
  23. package/build/entity-types/nav-menu.js +6 -0
  24. package/build/entity-types/nav-menu.js.map +1 -0
  25. package/build/entity-types/navigation-area.js +6 -0
  26. package/build/entity-types/navigation-area.js.map +1 -0
  27. package/build/entity-types/page.js +6 -0
  28. package/build/entity-types/page.js.map +1 -0
  29. package/build/entity-types/plugin.js +6 -0
  30. package/build/entity-types/plugin.js.map +1 -0
  31. package/build/entity-types/post.js +6 -0
  32. package/build/entity-types/post.js.map +1 -0
  33. package/build/entity-types/settings.js +6 -0
  34. package/build/entity-types/settings.js.map +1 -0
  35. package/build/entity-types/sidebar.js +6 -0
  36. package/build/entity-types/sidebar.js.map +1 -0
  37. package/build/entity-types/taxonomy.js +6 -0
  38. package/build/entity-types/taxonomy.js.map +1 -0
  39. package/build/entity-types/theme.js +6 -0
  40. package/build/entity-types/theme.js.map +1 -0
  41. package/build/entity-types/type.js +6 -0
  42. package/build/entity-types/type.js.map +1 -0
  43. package/build/entity-types/user.js +6 -0
  44. package/build/entity-types/user.js.map +1 -0
  45. package/build/entity-types/widget-type.js +6 -0
  46. package/build/entity-types/widget-type.js.map +1 -0
  47. package/build/entity-types/widget.js +6 -0
  48. package/build/entity-types/widget.js.map +1 -0
  49. package/build/entity-types/wp-template-part.js +6 -0
  50. package/build/entity-types/wp-template-part.js.map +1 -0
  51. package/build/entity-types/wp-template.js +6 -0
  52. package/build/entity-types/wp-template.js.map +1 -0
  53. package/build/fetch/__experimental-fetch-link-suggestions.js +2 -2
  54. package/build/fetch/__experimental-fetch-link-suggestions.js.map +1 -1
  55. package/build/hooks/constants.js +18 -0
  56. package/build/hooks/constants.js.map +1 -0
  57. package/build/hooks/memoize.js +18 -0
  58. package/build/hooks/memoize.js.map +1 -0
  59. package/build/hooks/use-entity-record.js +60 -0
  60. package/build/hooks/use-entity-record.js.map +1 -0
  61. package/build/hooks/use-entity-records.js +77 -0
  62. package/build/hooks/use-entity-records.js.map +1 -0
  63. package/build/hooks/use-query-select.js +130 -0
  64. package/build/hooks/use-query-select.js.map +1 -0
  65. package/build/index.js +34 -3
  66. package/build/index.js.map +1 -1
  67. package/build/reducer.js +1 -1
  68. package/build/reducer.js.map +1 -1
  69. package/build/resolvers.js +7 -17
  70. package/build/resolvers.js.map +1 -1
  71. package/build-module/actions.js +9 -13
  72. package/build-module/actions.js.map +1 -1
  73. package/build-module/batch/create-batch.js +17 -12
  74. package/build-module/batch/create-batch.js.map +1 -1
  75. package/build-module/entities.js +1 -1
  76. package/build-module/entities.js.map +1 -1
  77. package/build-module/entity-types/attachment.js +2 -0
  78. package/build-module/entity-types/attachment.js.map +1 -0
  79. package/build-module/entity-types/base-entity-types.js +39 -0
  80. package/build-module/entity-types/base-entity-types.js.map +1 -0
  81. package/build-module/entity-types/comment.js +2 -0
  82. package/build-module/entity-types/comment.js.map +1 -0
  83. package/build-module/entity-types/helpers.js +2 -0
  84. package/build-module/entity-types/helpers.js.map +1 -0
  85. package/build-module/entity-types/index.js +2 -0
  86. package/build-module/entity-types/index.js.map +1 -0
  87. package/build-module/entity-types/menu-location.js +2 -0
  88. package/build-module/entity-types/menu-location.js.map +1 -0
  89. package/build-module/entity-types/nav-menu-item.js +2 -0
  90. package/build-module/entity-types/nav-menu-item.js.map +1 -0
  91. package/build-module/entity-types/nav-menu.js +2 -0
  92. package/build-module/entity-types/nav-menu.js.map +1 -0
  93. package/build-module/entity-types/navigation-area.js +2 -0
  94. package/build-module/entity-types/navigation-area.js.map +1 -0
  95. package/build-module/entity-types/page.js +2 -0
  96. package/build-module/entity-types/page.js.map +1 -0
  97. package/build-module/entity-types/plugin.js +2 -0
  98. package/build-module/entity-types/plugin.js.map +1 -0
  99. package/build-module/entity-types/post.js +2 -0
  100. package/build-module/entity-types/post.js.map +1 -0
  101. package/build-module/entity-types/settings.js +2 -0
  102. package/build-module/entity-types/settings.js.map +1 -0
  103. package/build-module/entity-types/sidebar.js +2 -0
  104. package/build-module/entity-types/sidebar.js.map +1 -0
  105. package/build-module/entity-types/taxonomy.js +2 -0
  106. package/build-module/entity-types/taxonomy.js.map +1 -0
  107. package/build-module/entity-types/theme.js +2 -0
  108. package/build-module/entity-types/theme.js.map +1 -0
  109. package/build-module/entity-types/type.js +2 -0
  110. package/build-module/entity-types/type.js.map +1 -0
  111. package/build-module/entity-types/user.js +2 -0
  112. package/build-module/entity-types/user.js.map +1 -0
  113. package/build-module/entity-types/widget-type.js +2 -0
  114. package/build-module/entity-types/widget-type.js.map +1 -0
  115. package/build-module/entity-types/widget.js +2 -0
  116. package/build-module/entity-types/widget.js.map +1 -0
  117. package/build-module/entity-types/wp-template-part.js +2 -0
  118. package/build-module/entity-types/wp-template-part.js.map +1 -0
  119. package/build-module/entity-types/wp-template.js +2 -0
  120. package/build-module/entity-types/wp-template.js.map +1 -0
  121. package/build-module/fetch/__experimental-fetch-link-suggestions.js +2 -2
  122. package/build-module/fetch/__experimental-fetch-link-suggestions.js.map +1 -1
  123. package/build-module/hooks/constants.js +10 -0
  124. package/build-module/hooks/constants.js.map +1 -0
  125. package/build-module/hooks/memoize.js +7 -0
  126. package/build-module/hooks/memoize.js.map +1 -0
  127. package/build-module/hooks/use-entity-record.js +49 -0
  128. package/build-module/hooks/use-entity-record.js.map +1 -0
  129. package/build-module/hooks/use-entity-records.js +65 -0
  130. package/build-module/hooks/use-entity-records.js.map +1 -0
  131. package/build-module/hooks/use-query-select.js +116 -0
  132. package/build-module/hooks/use-query-select.js.map +1 -0
  133. package/build-module/index.js +4 -2
  134. package/build-module/index.js.map +1 -1
  135. package/build-module/reducer.js +1 -1
  136. package/build-module/reducer.js.map +1 -1
  137. package/build-module/resolvers.js +8 -18
  138. package/build-module/resolvers.js.map +1 -1
  139. package/package.json +15 -11
  140. package/src/actions.js +9 -13
  141. package/src/batch/create-batch.js +16 -12
  142. package/src/entities.js +1 -1
  143. package/src/entity-types/README.md +193 -0
  144. package/src/entity-types/attachment.ts +146 -0
  145. package/src/entity-types/base-entity-types.ts +36 -0
  146. package/src/entity-types/comment.ts +96 -0
  147. package/src/entity-types/helpers.ts +153 -0
  148. package/src/entity-types/index.ts +72 -0
  149. package/src/entity-types/menu-location.ts +29 -0
  150. package/src/entity-types/nav-menu-item.ts +106 -0
  151. package/src/entity-types/nav-menu.ts +53 -0
  152. package/src/entity-types/navigation-area.ts +29 -0
  153. package/src/entity-types/page.ts +144 -0
  154. package/src/entity-types/plugin.ts +74 -0
  155. package/src/entity-types/post.ts +153 -0
  156. package/src/entity-types/settings.ts +93 -0
  157. package/src/entity-types/sidebar.ts +60 -0
  158. package/src/entity-types/taxonomy.ts +92 -0
  159. package/src/entity-types/theme.ts +222 -0
  160. package/src/entity-types/type.ts +80 -0
  161. package/src/entity-types/user.ts +109 -0
  162. package/src/entity-types/widget-type.ts +37 -0
  163. package/src/entity-types/widget.ts +64 -0
  164. package/src/entity-types/wp-template-part.ts +94 -0
  165. package/src/entity-types/wp-template.ts +94 -0
  166. package/src/fetch/__experimental-fetch-link-suggestions.js +2 -2
  167. package/src/hooks/constants.ts +7 -0
  168. package/src/hooks/memoize.js +7 -0
  169. package/src/hooks/test/use-entity-record.js +74 -0
  170. package/src/hooks/test/use-entity-records.js +78 -0
  171. package/src/hooks/test/use-query-select.js +194 -0
  172. package/src/hooks/use-entity-record.ts +72 -0
  173. package/src/hooks/use-entity-records.ts +89 -0
  174. package/src/hooks/use-query-select.ts +131 -0
  175. package/src/index.js +3 -1
  176. package/src/locks/test/engine.js +13 -13
  177. package/src/locks/test/reducer.js +3 -3
  178. package/src/locks/test/utils.js +2 -2
  179. package/src/reducer.js +1 -1
  180. package/src/resolvers.js +6 -17
  181. package/src/test/resolvers.js +17 -23
  182. package/src/test/selectors.js +2 -3
@@ -103,7 +103,7 @@ const fetchLinkSuggestions = async (
103
103
  };
104
104
  } );
105
105
  } )
106
- .catch( () => [] ) // fail by returning no results
106
+ .catch( () => [] ) // Fail by returning no results.
107
107
  );
108
108
  }
109
109
 
@@ -156,7 +156,7 @@ const fetchLinkSuggestions = async (
156
156
  return Promise.all( queries ).then( ( results ) => {
157
157
  return results
158
158
  .reduce(
159
- ( accumulator, current ) => accumulator.concat( current ), //flatten list
159
+ ( accumulator, current ) => accumulator.concat( current ), // Flatten list.
160
160
  []
161
161
  )
162
162
  .filter(
@@ -0,0 +1,7 @@
1
+ /* eslint-disable-next-line no-shadow */
2
+ export enum Status {
3
+ Idle = 'IDLE',
4
+ Resolving = 'RESOLVING',
5
+ Error = 'ERROR',
6
+ Success = 'SUCCESS',
7
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import memoize from 'memize';
5
+
6
+ // re-export due to restrictive esModuleInterop setting
7
+ export default memoize;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import triggerFetch from '@wordpress/api-fetch';
5
+ import { createRegistry, RegistryProvider } from '@wordpress/data';
6
+
7
+ jest.mock( '@wordpress/api-fetch' );
8
+
9
+ /**
10
+ * External dependencies
11
+ */
12
+ import { act, render } from '@testing-library/react';
13
+
14
+ /**
15
+ * Internal dependencies
16
+ */
17
+ import { store as coreDataStore } from '../../index';
18
+ import useEntityRecord from '../use-entity-record';
19
+
20
+ describe( 'useEntityRecord', () => {
21
+ let registry;
22
+ beforeEach( () => {
23
+ jest.useFakeTimers();
24
+
25
+ registry = createRegistry();
26
+ registry.register( coreDataStore );
27
+ } );
28
+
29
+ afterEach( () => {
30
+ jest.runOnlyPendingTimers();
31
+ jest.useRealTimers();
32
+ } );
33
+
34
+ const TEST_RECORD = { id: 1, hello: 'world' };
35
+
36
+ it( 'resolves the entity record when missing from the state', async () => {
37
+ // Provide response
38
+ triggerFetch.mockImplementation( () => TEST_RECORD );
39
+
40
+ let data;
41
+ const TestComponent = () => {
42
+ data = useEntityRecord( 'root', 'widget', 1 );
43
+ return <div />;
44
+ };
45
+ render(
46
+ <RegistryProvider value={ registry }>
47
+ <TestComponent />
48
+ </RegistryProvider>
49
+ );
50
+
51
+ expect( data ).toEqual( {
52
+ records: undefined,
53
+ hasResolved: false,
54
+ isResolving: false,
55
+ status: 'IDLE',
56
+ } );
57
+
58
+ await act( async () => {
59
+ jest.advanceTimersByTime( 1 );
60
+ } );
61
+
62
+ // Fetch request should have been issued
63
+ expect( triggerFetch ).toHaveBeenCalledWith( {
64
+ path: '/wp/v2/widgets/1?context=edit',
65
+ } );
66
+
67
+ expect( data ).toEqual( {
68
+ record: { hello: 'world', id: 1 },
69
+ hasResolved: true,
70
+ isResolving: false,
71
+ status: 'SUCCESS',
72
+ } );
73
+ } );
74
+ } );
@@ -0,0 +1,78 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import triggerFetch from '@wordpress/api-fetch';
5
+ import { createRegistry, RegistryProvider } from '@wordpress/data';
6
+
7
+ jest.mock( '@wordpress/api-fetch' );
8
+
9
+ /**
10
+ * External dependencies
11
+ */
12
+ import { act, render } from '@testing-library/react';
13
+
14
+ /**
15
+ * Internal dependencies
16
+ */
17
+ import { store as coreDataStore } from '../../index';
18
+ import useEntityRecords from '../use-entity-records';
19
+
20
+ describe( 'useEntityRecords', () => {
21
+ let registry;
22
+ beforeEach( () => {
23
+ jest.useFakeTimers();
24
+
25
+ registry = createRegistry();
26
+ registry.register( coreDataStore );
27
+ } );
28
+
29
+ afterEach( () => {
30
+ jest.runOnlyPendingTimers();
31
+ jest.useRealTimers();
32
+ } );
33
+
34
+ const TEST_RECORDS = [
35
+ { id: 1, hello: 'world1' },
36
+ { id: 2, hello: 'world2' },
37
+ { id: 3, hello: 'world3' },
38
+ ];
39
+
40
+ it( 'resolves the entity records when missing from the state', async () => {
41
+ // Provide response
42
+ triggerFetch.mockImplementation( () => TEST_RECORDS );
43
+
44
+ let data;
45
+ const TestComponent = () => {
46
+ data = useEntityRecords( 'root', 'widget', { status: 'draft' } );
47
+ return <div />;
48
+ };
49
+ render(
50
+ <RegistryProvider value={ registry }>
51
+ <TestComponent />
52
+ </RegistryProvider>
53
+ );
54
+
55
+ expect( data ).toEqual( {
56
+ records: null,
57
+ hasResolved: false,
58
+ isResolving: false,
59
+ status: 'IDLE',
60
+ } );
61
+
62
+ await act( async () => {
63
+ jest.advanceTimersByTime( 1 );
64
+ } );
65
+
66
+ // Fetch request should have been issued
67
+ expect( triggerFetch ).toHaveBeenCalledWith( {
68
+ path: '/wp/v2/widgets?context=edit&status=draft',
69
+ } );
70
+
71
+ expect( data ).toEqual( {
72
+ records: TEST_RECORDS,
73
+ hasResolved: true,
74
+ isResolving: false,
75
+ status: 'SUCCESS',
76
+ } );
77
+ } );
78
+ } );
@@ -0,0 +1,194 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ createReduxStore,
6
+ createRegistry,
7
+ RegistryProvider,
8
+ } from '@wordpress/data';
9
+
10
+ /**
11
+ * External dependencies
12
+ */
13
+ import { act, render } from '@testing-library/react';
14
+
15
+ /**
16
+ * Internal dependencies
17
+ */
18
+ import useQuerySelect from '../use-query-select';
19
+
20
+ describe( 'useQuerySelect', () => {
21
+ let registry;
22
+ beforeEach( () => {
23
+ jest.useFakeTimers();
24
+
25
+ registry = createRegistry();
26
+ registry.registerStore( 'testStore', {
27
+ reducer: () => ( { foo: 'bar' } ),
28
+ selectors: {
29
+ getFoo: ( state ) => state.foo,
30
+ testSelector: ( state, key ) => state[ key ],
31
+ },
32
+ } );
33
+ } );
34
+
35
+ afterEach( () => {
36
+ jest.runOnlyPendingTimers();
37
+ jest.useRealTimers();
38
+ } );
39
+
40
+ const getTestComponent = ( mapSelectSpy, dependencyKey ) => ( props ) => {
41
+ const dependencies = props[ dependencyKey ];
42
+ mapSelectSpy.mockImplementation( ( select ) => ( {
43
+ results: select( 'testStore' ).testSelector( props.keyName ),
44
+ } ) );
45
+ const data = useQuerySelect( mapSelectSpy, [ dependencies ] );
46
+ return <div>{ data.results.data }</div>;
47
+ };
48
+
49
+ it( 'passes the relevant data to the component', () => {
50
+ const selectSpy = jest.fn();
51
+ const TestComponent = jest
52
+ .fn()
53
+ .mockImplementation( getTestComponent( selectSpy, 'keyName' ) );
54
+ const testInstance = render(
55
+ <RegistryProvider value={ registry }>
56
+ <TestComponent keyName="foo" />
57
+ </RegistryProvider>
58
+ );
59
+ // 2 times expected
60
+ // - 1 for initial mount
61
+ // - 1 for after mount before subscription set.
62
+ expect( selectSpy ).toHaveBeenCalledTimes( 2 );
63
+ expect( TestComponent ).toHaveBeenCalledTimes( 2 );
64
+
65
+ // ensure expected state was rendered
66
+ expect( testInstance.findByText( 'bar' ) ).toBeTruthy();
67
+ } );
68
+
69
+ it( 'uses memoized selectors', () => {
70
+ const selectors = [];
71
+ const TestComponent = jest.fn().mockImplementation( ( props ) => {
72
+ useQuerySelect(
73
+ function ( query ) {
74
+ selectors.push( query( 'testStore' ) );
75
+ selectors.push( query( 'testStore' ) );
76
+ return null;
77
+ },
78
+ [ props.keyName ]
79
+ );
80
+ return <div />;
81
+ } );
82
+
83
+ render(
84
+ <RegistryProvider value={ registry }>
85
+ <TestComponent keyName="foo" />
86
+ </RegistryProvider>
87
+ );
88
+
89
+ // ensure the selectors were properly memoized
90
+ expect( selectors ).toHaveLength( 4 );
91
+ expect( selectors[ 0 ] ).toHaveProperty( 'testSelector' );
92
+ expect( selectors[ 0 ] ).toBe( selectors[ 1 ] );
93
+ expect( selectors[ 1 ] ).toBe( selectors[ 2 ] );
94
+
95
+ // Re-render
96
+ render(
97
+ <RegistryProvider value={ registry }>
98
+ <TestComponent keyName="bar" />
99
+ </RegistryProvider>
100
+ );
101
+
102
+ // ensure we still got the memoized results after re-rendering
103
+ expect( selectors ).toHaveLength( 8 );
104
+ expect( selectors[ 3 ] ).toHaveProperty( 'testSelector' );
105
+ expect( selectors[ 5 ] ).toBe( selectors[ 6 ] );
106
+ } );
107
+
108
+ it( 'returns the expected "response" details – no resolvers and arguments', () => {
109
+ let querySelectData;
110
+ const TestComponent = jest.fn().mockImplementation( () => {
111
+ querySelectData = useQuerySelect( function ( query ) {
112
+ return query( 'testStore' ).getFoo();
113
+ }, [] );
114
+ return <div />;
115
+ } );
116
+
117
+ render(
118
+ <RegistryProvider value={ registry }>
119
+ <TestComponent />
120
+ </RegistryProvider>
121
+ );
122
+
123
+ expect( querySelectData ).toEqual( {
124
+ data: 'bar',
125
+ isResolving: false,
126
+ hasResolved: false,
127
+ status: 'IDLE',
128
+ } );
129
+ } );
130
+
131
+ it( 'returns the expected "response" details – resolvers and arguments', async () => {
132
+ registry.register(
133
+ createReduxStore( 'resolverStore', {
134
+ reducer: ( state = { resolvedFoo: 0 }, action ) => {
135
+ if ( action?.type === 'RECEIVE_FOO' ) {
136
+ return { ...state, resolvedFoo: action.value };
137
+ }
138
+ return state;
139
+ },
140
+ actions: {
141
+ receiveFoo: ( value ) => ( {
142
+ type: 'RECEIVE_FOO',
143
+ value,
144
+ } ),
145
+ },
146
+ resolvers: {
147
+ getResolvedFoo: () => ( { dispatch } ) =>
148
+ dispatch.receiveFoo( 5 ),
149
+ },
150
+ selectors: {
151
+ getResolvedFoo: ( state, arg ) => state.resolvedFoo + arg,
152
+ },
153
+ } )
154
+ );
155
+
156
+ let querySelectData;
157
+ const TestComponent = jest.fn().mockImplementation( () => {
158
+ querySelectData = useQuerySelect( function ( query ) {
159
+ return query( 'resolverStore' ).getResolvedFoo( 10 );
160
+ }, [] );
161
+ return <div />;
162
+ } );
163
+
164
+ // Initial render, expect default values
165
+ render(
166
+ <RegistryProvider value={ registry }>
167
+ <TestComponent />
168
+ </RegistryProvider>
169
+ );
170
+ expect( querySelectData ).toEqual( {
171
+ data: 10,
172
+ isResolving: false,
173
+ hasResolved: false,
174
+ status: 'IDLE',
175
+ } );
176
+
177
+ await act( async () => {
178
+ jest.advanceTimersToNextTimer();
179
+ } );
180
+
181
+ // Re-render, expect resolved data
182
+ render(
183
+ <RegistryProvider value={ registry }>
184
+ <TestComponent />
185
+ </RegistryProvider>
186
+ );
187
+ expect( querySelectData ).toEqual( {
188
+ data: 15,
189
+ isResolving: false,
190
+ hasResolved: true,
191
+ status: 'SUCCESS',
192
+ } );
193
+ } );
194
+ } );
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import useQuerySelect from './use-query-select';
5
+ import { store as coreStore } from '../';
6
+ import { Status } from './constants';
7
+
8
+ interface EntityRecordResolution< RecordType > {
9
+ /** The requested entity record */
10
+ record: RecordType | null;
11
+
12
+ /**
13
+ * Is the record still being resolved?
14
+ */
15
+ isResolving: boolean;
16
+
17
+ /**
18
+ * Is the record resolved by now?
19
+ */
20
+ hasResolved: boolean;
21
+
22
+ /** Resolution status */
23
+ status: Status;
24
+ }
25
+
26
+ /**
27
+ * Resolves the specified entity record.
28
+ *
29
+ * @param kind Kind of the requested entity.
30
+ * @param name Name of the requested entity.
31
+ * @param recordId Record ID of the requested entity.
32
+ *
33
+ * @example
34
+ * ```js
35
+ * import { useEntityRecord } from '@wordpress/core-data';
36
+ *
37
+ * function PageTitleDisplay( { id } ) {
38
+ * const { record, isResolving } = useEntityRecord( 'postType', 'page', id );
39
+ *
40
+ * if ( isResolving ) {
41
+ * return 'Loading...';
42
+ * }
43
+ *
44
+ * return record.title;
45
+ * }
46
+ *
47
+ * // Rendered in the application:
48
+ * // <PageTitleDisplay id={ 1 } />
49
+ * ```
50
+ *
51
+ * In the above example, when `PageTitleDisplay` is rendered into an
52
+ * application, the page and the resolution details will be retrieved from
53
+ * the store state using `getEntityRecord()`, or resolved if missing.
54
+ *
55
+ * @return {EntityRecordResolution<RecordType>} Entity record data.
56
+ * @template RecordType
57
+ */
58
+ export default function __experimentalUseEntityRecord< RecordType >(
59
+ kind: string,
60
+ name: string,
61
+ recordId: string | number
62
+ ): EntityRecordResolution< RecordType > {
63
+ const { data: record, ...rest } = useQuerySelect(
64
+ ( query ) => query( coreStore ).getEntityRecord( kind, name, recordId ),
65
+ [ kind, name, recordId ]
66
+ );
67
+
68
+ return {
69
+ record,
70
+ ...rest,
71
+ };
72
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { addQueryArgs } from '@wordpress/url';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import useQuerySelect from './use-query-select';
10
+ import { store as coreStore } from '../';
11
+ import { Status } from './constants';
12
+
13
+ interface EntityRecordsResolution< RecordType > {
14
+ /** The requested entity record */
15
+ records: RecordType[] | null;
16
+
17
+ /**
18
+ * Is the record still being resolved?
19
+ */
20
+ isResolving: boolean;
21
+
22
+ /**
23
+ * Is the record resolved by now?
24
+ */
25
+ hasResolved: boolean;
26
+
27
+ /** Resolution status */
28
+ status: Status;
29
+ }
30
+
31
+ /**
32
+ * Resolves the specified entity records.
33
+ *
34
+ * @param kind Kind of the requested entities.
35
+ * @param name Name of the requested entities.
36
+ * @param queryArgs HTTP query for the requested entities.
37
+ * @example
38
+ * ```js
39
+ * import { useEntityRecord } from '@wordpress/core-data';
40
+ *
41
+ * function PageTitlesList() {
42
+ * const { records, isResolving } = useEntityRecords( 'postType', 'page' );
43
+ *
44
+ * if ( isResolving ) {
45
+ * return 'Loading...';
46
+ * }
47
+ *
48
+ * return (
49
+ * <ul>
50
+ * {records.map(( page ) => (
51
+ * <li>{ page.title }</li>
52
+ * ))}
53
+ * </ul>
54
+ * );
55
+ * }
56
+ *
57
+ * // Rendered in the application:
58
+ * // <PageTitlesList />
59
+ * ```
60
+ *
61
+ * In the above example, when `PageTitlesList` is rendered into an
62
+ * application, the list of records and the resolution details will be retrieved from
63
+ * the store state using `getEntityRecords()`, or resolved if missing.
64
+ *
65
+ * @return {EntityRecordsResolution<RecordType>} Entity records data.
66
+ * @template RecordType
67
+ */
68
+ export default function __experimentalUseEntityRecords< RecordType >(
69
+ kind: string,
70
+ name: string,
71
+ queryArgs: unknown = {}
72
+ ): EntityRecordsResolution< RecordType > {
73
+ // Serialize queryArgs to a string that can be safely used as a React dep.
74
+ // We can't just pass queryArgs as one of the deps, because if it is passed
75
+ // as an object literal, then it will be a different object on each call even
76
+ // if the values remain the same.
77
+ const queryAsString = addQueryArgs( '', queryArgs );
78
+
79
+ const { data: records, ...rest } = useQuerySelect(
80
+ ( query ) =>
81
+ query( coreStore ).getEntityRecords( kind, name, queryArgs ),
82
+ [ kind, name, queryAsString ]
83
+ );
84
+
85
+ return {
86
+ records,
87
+ ...rest,
88
+ };
89
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useSelect } from '@wordpress/data';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import memoize from './memoize';
10
+ import { Status } from './constants';
11
+
12
+ export const META_SELECTORS = [
13
+ 'getIsResolving',
14
+ 'hasStartedResolution',
15
+ 'hasFinishedResolution',
16
+ 'isResolving',
17
+ 'getCachedResolvers',
18
+ ];
19
+
20
+ interface QuerySelectResponse {
21
+ /** the requested selector return value */
22
+ data: Object;
23
+
24
+ /** is the record still being resolved? Via the `getIsResolving` meta-selector */
25
+ isResolving: boolean;
26
+
27
+ /** was the resolution started? Via the `hasStartedResolution` meta-selector */
28
+ hasStarted: boolean;
29
+
30
+ /** has the resolution finished? Via the `hasFinishedResolution` meta-selector. */
31
+ hasResolved: boolean;
32
+ }
33
+
34
+ /**
35
+ * Like useSelect, but the selectors return objects containing
36
+ * both the original data AND the resolution info.
37
+ *
38
+ * @param {Function} mapQuerySelect see useSelect
39
+ * @param {Array} deps see useSelect
40
+ *
41
+ * @example
42
+ * ```js
43
+ * import { useQuerySelect } from '@wordpress/data';
44
+ * import { store as coreDataStore } from '@wordpress/core-data';
45
+ *
46
+ * function PageTitleDisplay( { id } ) {
47
+ * const { data: page, isResolving } = useQuerySelect( ( query ) => {
48
+ * return query( coreDataStore ).getEntityRecord( 'postType', 'page', id )
49
+ * }, [ id ] );
50
+ *
51
+ * if ( isResolving ) {
52
+ * return 'Loading...';
53
+ * }
54
+ *
55
+ * return page.title;
56
+ * }
57
+ *
58
+ * // Rendered in the application:
59
+ * // <PageTitleDisplay id={ 10 } />
60
+ * ```
61
+ *
62
+ * In the above example, when `PageTitleDisplay` is rendered into an
63
+ * application, the page and the resolution details will be retrieved from
64
+ * the store state using the `mapSelect` callback on `useQuerySelect`.
65
+ *
66
+ * If the id prop changes then any page in the state for that id is
67
+ * retrieved. If the id prop doesn't change and other props are passed in
68
+ * that do change, the title will not change because the dependency is just
69
+ * the id.
70
+ * @see useSelect
71
+ *
72
+ * @return {QuerySelectResponse} Queried data.
73
+ */
74
+ export default function __experimentalUseQuerySelect( mapQuerySelect, deps ) {
75
+ return useSelect( ( select, registry ) => {
76
+ const resolve = ( store ) => enrichSelectors( select( store ) );
77
+ return mapQuerySelect( resolve, registry );
78
+ }, deps );
79
+ }
80
+
81
+ type QuerySelector = ( ...args ) => QuerySelectResponse;
82
+ interface EnrichedSelectors {
83
+ [ key: string ]: QuerySelector;
84
+ }
85
+
86
+ /**
87
+ * Transform simple selectors into ones that return an object with the
88
+ * original return value AND the resolution info.
89
+ *
90
+ * @param {Object} selectors Selectors to enrich
91
+ * @return {EnrichedSelectors} Enriched selectors
92
+ */
93
+ const enrichSelectors = memoize( ( selectors ) => {
94
+ const resolvers = {};
95
+ for ( const selectorName in selectors ) {
96
+ if ( META_SELECTORS.includes( selectorName ) ) {
97
+ continue;
98
+ }
99
+ Object.defineProperty( resolvers, selectorName, {
100
+ get: () => ( ...args ) => {
101
+ const { getIsResolving, hasFinishedResolution } = selectors;
102
+ const isResolving = !! getIsResolving( selectorName, args );
103
+ const hasResolved =
104
+ ! isResolving &&
105
+ hasFinishedResolution( selectorName, args );
106
+ const data = selectors[ selectorName ]( ...args );
107
+
108
+ let status;
109
+ if ( isResolving ) {
110
+ status = Status.Resolving;
111
+ } else if ( hasResolved ) {
112
+ if ( data ) {
113
+ status = Status.Success;
114
+ } else {
115
+ status = Status.Error;
116
+ }
117
+ } else {
118
+ status = Status.Idle;
119
+ }
120
+
121
+ return {
122
+ data,
123
+ status,
124
+ isResolving,
125
+ hasResolved,
126
+ };
127
+ },
128
+ } );
129
+ }
130
+ return resolvers;
131
+ } );
package/src/index.js CHANGED
@@ -59,7 +59,6 @@ const storeConfig = () => ( {
59
59
  actions: { ...actions, ...entityActions, ...createLocksActions() },
60
60
  selectors: { ...selectors, ...entitySelectors },
61
61
  resolvers: { ...resolvers, ...entityResolvers },
62
- __experimentalUseThunks: true,
63
62
  } );
64
63
 
65
64
  /**
@@ -74,5 +73,8 @@ export const store = createReduxStore( STORE_NAME, storeConfig() );
74
73
  register( store );
75
74
 
76
75
  export { default as EntityProvider } from './entity-provider';
76
+ export { default as __experimentalUseEntityRecord } from './hooks/use-entity-record';
77
+ export { default as __experimentalUseEntityRecords } from './hooks/use-entity-records';
77
78
  export * from './entity-provider';
78
79
  export * from './fetch';
80
+ export * from './entity-types';