diff --git a/packages/data/src/components/use-select/index.js b/packages/data/src/components/use-select/index.js index 5767b375cf3e71..bc9d2f71bdedda 100644 --- a/packages/data/src/components/use-select/index.js +++ b/packages/data/src/components/use-select/index.js @@ -153,6 +153,7 @@ export default function useSelect( mapSelect, deps ) { let mapOutput; + let selectorRan = false; if ( _mapSelect ) { mapOutput = latestMapOutput.current; const hasReplacedRegistry = latestRegistry.current !== registry; @@ -168,6 +169,7 @@ export default function useSelect( mapSelect, deps ) { ) { try { mapOutput = wrapSelect( _mapSelect ); + selectorRan = true; } catch ( error ) { let errorMessage = `An error occurred while running 'mapSelect': ${ error.message }`; @@ -191,7 +193,9 @@ export default function useSelect( mapSelect, deps ) { latestRegistry.current = registry; latestMapSelect.current = _mapSelect; latestIsAsync.current = isAsync; - latestMapOutput.current = mapOutput; + if ( selectorRan ) { + latestMapOutput.current = mapOutput; + } latestMapOutputError.current = undefined; } ); @@ -310,9 +314,11 @@ export function useSuspenseSelect( mapSelect, deps ) { const hasReplacedMapSelect = latestMapSelect.current !== _mapSelect; const hasLeftAsyncMode = latestIsAsync.current && ! isAsync; + let selectorRan = false; if ( hasReplacedRegistry || hasReplacedMapSelect || hasLeftAsyncMode ) { try { mapOutput = wrapSelect( _mapSelect ); + selectorRan = true; } catch ( error ) { mapOutputError = error; } @@ -322,7 +328,9 @@ export function useSuspenseSelect( mapSelect, deps ) { latestRegistry.current = registry; latestMapSelect.current = _mapSelect; latestIsAsync.current = isAsync; - latestMapOutput.current = mapOutput; + if ( selectorRan ) { + latestMapOutput.current = mapOutput; + } latestMapOutputError.current = mapOutputError; } ); diff --git a/packages/data/src/components/use-select/test/index.js b/packages/data/src/components/use-select/test/index.js index 6e0471f7b4449d..3337ed976139d2 100644 --- a/packages/data/src/components/use-select/test/index.js +++ b/packages/data/src/components/use-select/test/index.js @@ -1,12 +1,12 @@ /** * External dependencies */ -import { act, render } from '@testing-library/react'; +import { act, render, fireEvent } from '@testing-library/react'; /** * WordPress dependencies */ -import { useState, useReducer } from '@wordpress/element'; +import { Component, useState, useReducer } from '@wordpress/element'; /** * Internal dependencies @@ -434,6 +434,63 @@ describe( 'useSelect', () => { ); } ); + it( 'captures state changes scheduled between render and effect', () => { + registry.registerStore( 'store-1', counterStore ); + + class ChildComponent extends Component { + componentDidUpdate( prevProps ) { + if ( + this.props.childShouldDispatch && + this.props.childShouldDispatch !== + prevProps.childShouldDispatch + ) { + registry.dispatch( 'store-1' ).increment(); + } + } + + render() { + return null; + } + } + + const selectCount1AndDep = jest.fn( ( select ) => ( { + count1: select( 'store-1' ).getCounter(), + } ) ); + + const TestComponent = () => { + const [ childShouldDispatch, setChildShouldDispatch ] = + useState( false ); + const state = useSelect( selectCount1AndDep, [] ); + + return ( + <> +
count1:{ state.count1 }
+ + + + ); + }; + + const rendered = render( + + + + ); + + fireEvent.click( rendered.getByText( 'triggerChildDispatch' ) ); + + expect( selectCount1AndDep ).toHaveBeenCalledTimes( 3 ); + expect( rendered.getByRole( 'status' ) ).toHaveTextContent( + 'count1:1' + ); + } ); + it( 'handles registry selectors', () => { const getCount1And2 = createRegistrySelector( ( select ) => ( state ) => ( { diff --git a/packages/editor/src/components/provider/use-block-editor-settings.native.js b/packages/editor/src/components/provider/use-block-editor-settings.native.js index 64522abd27e181..53d703ee762faa 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.native.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.native.js @@ -16,25 +16,24 @@ function useNativeBlockEditorSettings( settings, hasTemplate ) { const editorSettings = useBlockEditorSettings( settings, hasTemplate ); const supportReusableBlock = capabilities.reusableBlock === true; - const { reusableBlocks } = useSelect( - ( select ) => ( { - reusableBlocks: supportReusableBlock - ? select( coreStore ).getEntityRecords( - 'postType', - 'wp_block', - // Unbounded queries are not supported on native so as a workaround, we set per_page with the maximum value that native version can handle. - // Related issue: https://github.com/wordpress-mobile/gutenberg-mobile/issues/2661 - { per_page: 100 } - ) - : [], - } ), + const { reusableBlocks, isTitleSelected } = useSelect( + ( select ) => { + return { + reusableBlocks: supportReusableBlock + ? select( coreStore ).getEntityRecords( + 'postType', + 'wp_block', + // Unbounded queries are not supported on native so as a workaround, we set per_page with the maximum value that native version can handle. + // Related issue: https://github.com/wordpress-mobile/gutenberg-mobile/issues/2661 + { per_page: 100 } + ) + : [], + isTitleSelected: select( editorStore ).isPostTitleSelected(), + }; + }, [ supportReusableBlock ] ); - const { isTitleSelected } = useSelect( ( select ) => ( { - isTitleSelected: select( editorStore ).isPostTitleSelected(), - } ) ); - return useMemo( () => ( { ...editorSettings,