diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 3e5373eda6d6a..e767c2d306b3d 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -58,7 +58,7 @@ export const getCurrentUser = */ export const getEntityRecord = ( kind, name, key = '', query ) => - async ( { select, dispatch } ) => { + async ( { select, dispatch, registry } ) => { const configs = await dispatch( getOrLoadEntitiesConfig( kind, name ) ); const entityConfig = configs.find( ( config ) => config.name === name && config.kind === kind @@ -166,9 +166,12 @@ export const getEntityRecord = } const record = await apiFetch( { path } ); - dispatch.receiveEntityRecords( kind, name, record, query ); + registry.batch( () => { + dispatch.receiveEntityRecords( kind, name, record, query ); + dispatch.__unstableReleaseStoreLock( lock ); + } ); } - } finally { + } catch ( e ) { dispatch.__unstableReleaseStoreLock( lock ); } }; @@ -414,12 +417,14 @@ export const canUser = permissions[ actionName ] = allowedMethods.includes( methodName ); } - for ( const action of retrievedActions ) { - dispatch.receiveUserPermission( - `${ action }/${ resourcePath }`, - permissions[ action ] - ); - } + registry.batch( () => { + for ( const action of retrievedActions ) { + dispatch.receiveUserPermission( + `${ action }/${ resourcePath }`, + permissions[ action ] + ); + } + } ); }; /** @@ -668,7 +673,7 @@ export const getUserPatternCategories = export const getNavigationFallbackId = () => - async ( { dispatch, select } ) => { + async ( { dispatch, select, registry } ) => { const fallback = await apiFetch( { path: addQueryArgs( '/wp-block-editor/v1/navigation-fallback', { _embed: true, @@ -677,33 +682,36 @@ export const getNavigationFallbackId = const record = fallback?._embedded?.self; - dispatch.receiveNavigationFallbackId( fallback?.id ); - - if ( record ) { - // If the fallback is already in the store, don't invalidate navigation queries. - // Otherwise, invalidate the cache for the scenario where there were no Navigation - // posts in the state and the fallback created one. - const existingFallbackEntityRecord = select.getEntityRecord( - 'postType', - 'wp_navigation', - fallback.id - ); - const invalidateNavigationQueries = ! existingFallbackEntityRecord; - dispatch.receiveEntityRecords( - 'postType', - 'wp_navigation', - record, - undefined, - invalidateNavigationQueries - ); + registry.batch( () => { + dispatch.receiveNavigationFallbackId( fallback?.id ); + + if ( record ) { + // If the fallback is already in the store, don't invalidate navigation queries. + // Otherwise, invalidate the cache for the scenario where there were no Navigation + // posts in the state and the fallback created one. + const existingFallbackEntityRecord = select.getEntityRecord( + 'postType', + 'wp_navigation', + fallback.id + ); + const invalidateNavigationQueries = + ! existingFallbackEntityRecord; + dispatch.receiveEntityRecords( + 'postType', + 'wp_navigation', + record, + undefined, + invalidateNavigationQueries + ); - // Resolve to avoid further network requests. - dispatch.finishResolution( 'getEntityRecord', [ - 'postType', - 'wp_navigation', - fallback.id, - ] ); - } + // Resolve to avoid further network requests. + dispatch.finishResolution( 'getEntityRecord', [ + 'postType', + 'wp_navigation', + fallback.id, + ] ); + } + } ); }; export const getDefaultTemplateId = @@ -730,7 +738,7 @@ export const getDefaultTemplateId = */ export const getRevisions = ( kind, name, recordKey, query = {} ) => - async ( { dispatch } ) => { + async ( { dispatch, registry } ) => { const configs = await dispatch( getOrLoadEntitiesConfig( kind, name ) ); const entityConfig = configs.find( ( config ) => config.name === name && config.kind === kind @@ -797,40 +805,42 @@ export const getRevisions = } ); } - dispatch.receiveRevisions( - kind, - name, - recordKey, - records, - query, - false, - meta - ); + registry.batch( () => { + dispatch.receiveRevisions( + kind, + name, + recordKey, + records, + query, + false, + meta + ); - // When requesting all fields, the list of results can be used to - // resolve the `getRevision` selector in addition to `getRevisions`. - if ( ! query?._fields && ! query.context ) { - const key = entityConfig.key || DEFAULT_ENTITY_KEY; - const resolutionsArgs = records - .filter( ( record ) => record[ key ] ) - .map( ( record ) => [ - kind, - name, - recordKey, - record[ key ], - ] ); - - dispatch( { - type: 'START_RESOLUTIONS', - selectorName: 'getRevision', - args: resolutionsArgs, - } ); - dispatch( { - type: 'FINISH_RESOLUTIONS', - selectorName: 'getRevision', - args: resolutionsArgs, - } ); - } + // When requesting all fields, the list of results can be used to + // resolve the `getRevision` selector in addition to `getRevisions`. + if ( ! query?._fields && ! query.context ) { + const key = entityConfig.key || DEFAULT_ENTITY_KEY; + const resolutionsArgs = records + .filter( ( record ) => record[ key ] ) + .map( ( record ) => [ + kind, + name, + recordKey, + record[ key ], + ] ); + + dispatch( { + type: 'START_RESOLUTIONS', + selectorName: 'getRevision', + args: resolutionsArgs, + } ); + dispatch( { + type: 'FINISH_RESOLUTIONS', + selectorName: 'getRevision', + args: resolutionsArgs, + } ); + } + } ); } }; diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index 36265165da96e..e51188de81ad3 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -27,6 +27,7 @@ describe( 'getEntityRecord', () => { baseURLParams: { context: 'edit' }, }, ]; + const registry = { batch: ( callback ) => callback() }; beforeEach( async () => { triggerFetch.mockReset(); @@ -44,7 +45,11 @@ describe( 'getEntityRecord', () => { // Provide response triggerFetch.mockImplementation( () => POST_TYPE ); - await getEntityRecord( 'root', 'postType', 'post' )( { dispatch } ); + await getEntityRecord( + 'root', + 'postType', + 'post' + )( { dispatch, registry } ); // Fetch request should have been issued. expect( triggerFetch ).toHaveBeenCalledWith( { @@ -92,7 +97,7 @@ describe( 'getEntityRecord', () => { 'postType', 'post', query - )( { dispatch, select } ); + )( { dispatch, select, registry } ); // Check resolution cache for an existing entity that fulfills the request with query. expect( select.hasEntityRecords ).toHaveBeenCalledWith( @@ -289,6 +294,7 @@ describe( 'canUser', () => { select: jest.fn( () => ( { hasStartedResolution: () => false, } ) ), + batch: ( callback ) => callback(), }; triggerFetch.mockReset(); } ); @@ -391,6 +397,7 @@ describe( 'canUser', () => { select: () => ( { hasStartedResolution: ( _, [ action ] ) => action === 'read', } ), + batch: ( callback ) => callback(), }; triggerFetch.mockImplementation( () => ( { @@ -421,6 +428,7 @@ describe( 'canUser', () => { select: () => ( { hasStartedResolution: ( _, [ action ] ) => action === 'read', } ), + batch: ( callback ) => callback(), }; triggerFetch.mockImplementation( () => ( { @@ -459,6 +467,7 @@ describe( 'canUser', () => { select: () => ( { hasStartedResolution: ( _, [ action ] ) => action === 'create', } ), + batch: ( callback ) => callback(), }; triggerFetch.mockImplementation( () => ( {