Skip to content

Conversation

@adamziel
Copy link
Contributor

@adamziel adamziel commented Mar 8, 2022

Description

A part of #38135

Following up on an excellent discussion from #38827, this PR takes a closer look at the conditional execution of getEntityRecords to make it a viable tool in cases such as this one:

	const { menuItems, hasResolvedMenuItems = false } = useSelect(
		( select ) => {
			if ( ! menuId ) {
				return {};
			}

			const { getMenuItems, hasFinishedResolution } = select( coreStore );
			const query = {
				menus: menuId,
				per_page: -1,
				context: 'view',
			};
			return {
				menuItems: getMenuItems( query ),
				hasResolvedMenuItems: hasFinishedResolution( 'getMenuItems', [
					query,
				] ),
			};
		},
		[ menuId ]
	);

With the new option proposed in this PR, the snippet above would look like this instead:

	const { records, hasResolved } = useEntityRecords(
		'root',
		'menuItem',
		{
			menus: menuId,
			per_page: -1,
			context: 'view',
		},
		{ enabled: !! menuId }
	);

As demonstrated in the update made to use-navigation-entities.js.

Test plan

Confirm the tests are green. The relevant navigation block use-cases are covered by e2e so there should be no additional testing required.

Alternatives

@kevin940726 suggested opening useQuerySelect API as an alternative:

useQuerySelect((query) => {
  if (enabled) {
    return query(coreStore).selectAll('root', 'menu', { per_page: -1, context: 'view' });
  }
});

It has a few upsides, such as enabling the use of conditional, loops, infinite loading, etc.

It could make a great follow-up discussion – I still think this PR has merit on its own.

cc @gziolo @getdave @kevin940726 @noisysocks

@adamziel adamziel added [Type] Code Quality Issues or PRs that relate to code quality [Package] Core data /packages/core-data labels Mar 8, 2022
@adamziel adamziel self-assigned this Mar 8, 2022
@github-actions
Copy link

github-actions bot commented Mar 8, 2022

Size Change: -5 B (0%)

Total Size: 1.16 MB

Filename Size Change
build/block-library/index.min.js 168 kB -40 B (0%)
build/core-data/index.min.js 14.2 kB +35 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 993 B
build/admin-manifest/index.min.js 1.24 kB
build/annotations/index.min.js 2.77 kB
build/api-fetch/index.min.js 2.27 kB
build/autop/index.min.js 2.15 kB
build/blob/index.min.js 487 B
build/block-directory/index.min.js 6.49 kB
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-editor/index.min.js 145 kB
build/block-editor/style-rtl.css 15 kB
build/block-editor/style.css 15 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 111 B
build/block-library/blocks/audio/style.css 111 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 445 B
build/block-library/blocks/button/editor.css 445 B
build/block-library/blocks/button/style-rtl.css 560 B
build/block-library/blocks/button/style.css 560 B
build/block-library/blocks/buttons/editor-rtl.css 292 B
build/block-library/blocks/buttons/editor.css 292 B
build/block-library/blocks/buttons/style-rtl.css 275 B
build/block-library/blocks/buttons/style.css 275 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 103 B
build/block-library/blocks/code/style.css 103 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-template/style-rtl.css 127 B
build/block-library/blocks/comment-template/style.css 127 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-query-loop/editor-rtl.css 95 B
build/block-library/blocks/comments-query-loop/editor.css 95 B
build/block-library/blocks/cover/editor-rtl.css 546 B
build/block-library/blocks/cover/editor.css 547 B
build/block-library/blocks/cover/style-rtl.css 1.56 kB
build/block-library/blocks/cover/style.css 1.56 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 417 B
build/block-library/blocks/embed/style.css 417 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 255 B
build/block-library/blocks/file/style.css 255 B
build/block-library/blocks/file/view.min.js 353 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 965 B
build/block-library/blocks/gallery/editor.css 967 B
build/block-library/blocks/gallery/style-rtl.css 1.61 kB
build/block-library/blocks/gallery/style.css 1.61 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 159 B
build/block-library/blocks/group/editor.css 159 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 114 B
build/block-library/blocks/heading/style.css 114 B
build/block-library/blocks/html/editor-rtl.css 332 B
build/block-library/blocks/html/editor.css 333 B
build/block-library/blocks/image/editor-rtl.css 731 B
build/block-library/blocks/image/editor.css 730 B
build/block-library/blocks/image/style-rtl.css 529 B
build/block-library/blocks/image/style.css 535 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 199 B
build/block-library/blocks/latest-posts/editor.css 198 B
build/block-library/blocks/latest-posts/style-rtl.css 447 B
build/block-library/blocks/latest-posts/style.css 446 B
build/block-library/blocks/list/style-rtl.css 94 B
build/block-library/blocks/list/style.css 94 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 493 B
build/block-library/blocks/media-text/style.css 490 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 708 B
build/block-library/blocks/navigation-link/editor.css 706 B
build/block-library/blocks/navigation-link/style-rtl.css 94 B
build/block-library/blocks/navigation-link/style.css 94 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 299 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation-submenu/view.min.js 375 B
build/block-library/blocks/navigation/editor-rtl.css 2.03 kB
build/block-library/blocks/navigation/editor.css 2.04 kB
build/block-library/blocks/navigation/style-rtl.css 1.89 kB
build/block-library/blocks/navigation/style.css 1.88 kB
build/block-library/blocks/navigation/view.min.js 2.85 kB
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 363 B
build/block-library/blocks/page-list/editor.css 363 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 273 B
build/block-library/blocks/paragraph/style.css 273 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/style-rtl.css 446 B
build/block-library/blocks/post-comments-form/style.css 446 B
build/block-library/blocks/post-comments/style-rtl.css 521 B
build/block-library/blocks/post-comments/style.css 521 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 721 B
build/block-library/blocks/post-featured-image/editor.css 721 B
build/block-library/blocks/post-featured-image/style-rtl.css 153 B
build/block-library/blocks/post-featured-image/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 323 B
build/block-library/blocks/post-template/style.css 323 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 80 B
build/block-library/blocks/post-title/style.css 80 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/style-rtl.css 389 B
build/block-library/blocks/pullquote/style.css 388 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 234 B
build/block-library/blocks/query-pagination/style.css 231 B
build/block-library/blocks/query/editor-rtl.css 131 B
build/block-library/blocks/query/editor.css 132 B
build/block-library/blocks/quote/style-rtl.css 201 B
build/block-library/blocks/quote/style.css 201 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 397 B
build/block-library/blocks/search/style.css 398 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/editor-rtl.css 99 B
build/block-library/blocks/separator/editor.css 99 B
build/block-library/blocks/separator/style-rtl.css 233 B
build/block-library/blocks/separator/style.css 233 B
build/block-library/blocks/separator/theme-rtl.css 172 B
build/block-library/blocks/separator/theme.css 172 B
build/block-library/blocks/shortcode/editor-rtl.css 474 B
build/block-library/blocks/shortcode/editor.css 474 B
build/block-library/blocks/site-logo/editor-rtl.css 744 B
build/block-library/blocks/site-logo/editor.css 744 B
build/block-library/blocks/site-logo/style-rtl.css 181 B
build/block-library/blocks/site-logo/style.css 181 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 177 B
build/block-library/blocks/social-link/editor.css 177 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.37 kB
build/block-library/blocks/social-links/style.css 1.36 kB
build/block-library/blocks/spacer/editor-rtl.css 332 B
build/block-library/blocks/spacer/editor.css 332 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 471 B
build/block-library/blocks/table/editor.css 472 B
build/block-library/blocks/table/style-rtl.css 481 B
build/block-library/blocks/table/style.css 481 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 B
build/block-library/blocks/tag-cloud/style-rtl.css 226 B
build/block-library/blocks/tag-cloud/style.css 227 B
build/block-library/blocks/template-part/editor-rtl.css 235 B
build/block-library/blocks/template-part/editor.css 235 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 571 B
build/block-library/blocks/video/editor.css 572 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/common-rtl.css 934 B
build/block-library/common.css 932 B
build/block-library/editor-rtl.css 9.96 kB
build/block-library/editor.css 9.96 kB
build/block-library/reset-rtl.css 474 B
build/block-library/reset.css 474 B
build/block-library/style-rtl.css 11.4 kB
build/block-library/style.css 11.4 kB
build/block-library/theme-rtl.css 665 B
build/block-library/theme.css 670 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/blocks/index.min.js 46.5 kB
build/components/index.min.js 218 kB
build/components/style-rtl.css 15.6 kB
build/components/style.css 15.6 kB
build/compose/index.min.js 11.2 kB
build/customize-widgets/index.min.js 11.2 kB
build/customize-widgets/style-rtl.css 1.39 kB
build/customize-widgets/style.css 1.39 kB
build/data-controls/index.min.js 663 B
build/data/index.min.js 8.19 kB
build/date/index.min.js 31.9 kB
build/deprecated/index.min.js 518 B
build/dom-ready/index.min.js 336 B
build/dom/index.min.js 4.53 kB
build/edit-navigation/index.min.js 16.1 kB
build/edit-navigation/style-rtl.css 4.04 kB
build/edit-navigation/style.css 4.05 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/index.min.js 29.8 kB
build/edit-post/style-rtl.css 7.07 kB
build/edit-post/style.css 7.07 kB
build/edit-site/index.min.js 43.8 kB
build/edit-site/style-rtl.css 7.44 kB
build/edit-site/style.css 7.42 kB
build/edit-widgets/index.min.js 16.5 kB
build/edit-widgets/style-rtl.css 4.39 kB
build/edit-widgets/style.css 4.39 kB
build/editor/index.min.js 38.4 kB
build/editor/style-rtl.css 3.71 kB
build/editor/style.css 3.71 kB
build/element/index.min.js 4.29 kB
build/escape-html/index.min.js 548 B
build/format-library/index.min.js 6.62 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.66 kB
build/html-entities/index.min.js 454 B
build/i18n/index.min.js 3.79 kB
build/is-shallow-equal/index.min.js 535 B
build/keyboard-shortcuts/index.min.js 1.83 kB
build/keycodes/index.min.js 1.41 kB
build/list-reusable-blocks/index.min.js 1.75 kB
build/list-reusable-blocks/style-rtl.css 838 B
build/list-reusable-blocks/style.css 838 B
build/media-utils/index.min.js 2.94 kB
build/notices/index.min.js 957 B
build/nux/index.min.js 2.12 kB
build/nux/style-rtl.css 751 B
build/nux/style.css 749 B
build/plugins/index.min.js 1.98 kB
build/preferences/index.min.js 1.2 kB
build/primitives/index.min.js 949 B
build/priority-queue/index.min.js 611 B
build/react-i18n/index.min.js 704 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.69 kB
build/reusable-blocks/index.min.js 2.24 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 11.1 kB
build/server-side-render/index.min.js 1.61 kB
build/shortcode/index.min.js 1.52 kB
build/token-list/index.min.js 668 B
build/url/index.min.js 1.99 kB
build/viewport/index.min.js 1.08 kB
build/warning/index.min.js 280 B
build/widgets/index.min.js 7.21 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.07 kB

compressed-size-action

@noisysocks
Copy link
Member

Does the abstraction have to cover every use case? I'd say better to cover 80% of cases and keep using getMenuItems and hasFinishedResolution for cases like this.

@noisysocks
Copy link
Member

Alternatively maybe something simple is to make useEntityRecords return null when passed a falsey query.

const { records, hasResolved } = useEntityRecords(
	'root',
	'menuItem',
	menuId && {
		menus: menuId,
		per_page: -1,
		context: 'view',
	}
);

@getdave
Copy link
Contributor

getdave commented Mar 9, 2022

Thanks for exploring this.

Could you advise how you'd handle the following with the current proposal?

<button onClick={triggerFetchMenuItems}>Get the Menu Items</button>

I don't love the { execute: !! menuId } conditional in the API signature. Is this something we'd seen in other libraries hooks?

Does the abstraction have to cover every use case? I'd say better to cover 80% of cases and keep using getMenuItems and hasFinishedResolution for cases like this.

There's a reasonable argument here. That said, it does feel like we're saddling the logic for running the query into the hook rather than asking the consuming component to handle it.

@gziolo
Copy link
Member

gziolo commented Mar 9, 2022

Does the abstraction have to cover every use case? I'd say better to cover 80% of cases and keep using getMenuItems and hasFinishedResolution for cases like this.

This is exactly the same thought I have. While it addresses an edge case, the API is very confusing.

In general, avoiding running React hooks conditionally is limitation number one:

https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders

In practice, you might want to have this type of conditional execution for most of the React hooks that might trigger resource-intensive operations. I did a quick Google search and maybe some sort of a higher-order React hook like in https://dev.to/lexlohr/conditional-hooks-1fpj could do the trick.

Both the original proposal and the alternative shared by @noisysocks in #39288 (comment) workaround this limitation in a clever way.

There is another alternative, provide a local hook that does the same trick:

function useMenuItemEntityRecordsConditionally( execute, menuId ) {
	if ( ! execute ) {
		return {};
	} 
	const { records, hasResolved } = useEntityRecords(
		'root',
		'menuItem',
		{
			menus: menuId,
			per_page: -1,
			context: 'view',
		},
	);
	return { records, hasResolved };
}

@kevin940726
Copy link
Member

Could you advise how you'd handle the following with the current proposal?

<button onClick={triggerFetchMenuItems}>Get the Menu Items</button>

To me, this seems like a use case best handled by dispatching an action imperatively rather than using this hook. However, I could imagine something like a refetch similar to the one in react-query would be useful in some cases.

I don't love the { execute: !! menuId } conditional in the API signature. Is this something we'd seen in other libraries hooks?

It's used in react-query with the name enabled, which I think might be a better name than the verb execute.

There is another alternative, provide a local hook that does the same trick:

I think this still violates the rules of hooks, you can't put hooks inside conditional, even when it's inside a custom hook.

@getdave
Copy link
Contributor

getdave commented Mar 9, 2022

the name enabled, which I think might be a better name than the verb execute.

Correct. The other term is potentially quite emotive and the enable/disable is a much better description of what we're attempting.

@gziolo
Copy link
Member

gziolo commented Mar 9, 2022

There is another alternative, provide a local hook that does the same trick:

I think this still violates the rules of hooks, you can't put hooks inside conditional, even when it's inside a custom hook.

Right, good catch. Indeed, it technically goes against those rules. In practice it's exactly the same execution flow as in other proposals, it just uses the use prefix internally after the condition 🤷🏻
The real question here is whether those React hooks rules are something we should be worried about here. We are trying to optimize the execution flow so if there is no risk something goes wrong, maybe we can ignore those rules here.

Edit: useEntityRecords calls internally useSelect that uses some basic React hooks like useCallback or useRef which means they probably should be called on every render. It's hard to tell really.

@kevin940726
Copy link
Member

The real question here is whether those React hooks rules are something we should be worried about here. We are trying to optimize the execution flow so if there is no risk something goes wrong, maybe we can ignore those rules here.

I don't think we can, and I don't suggest we do. IIRC, the ordering of the hooks is the hard requirement of the hook rules, it's an error level rule in eslint, not the same as exhaustive deps which is only a warning level rule. I think conditionally calling a hook will break the application since React will lose track of the hooks the component's calling. Here's a deep dive into why calling order matters in React hooks.

@adamziel
Copy link
Contributor Author

Given the discussion, what is your stance on this API now @gziolo? Do you have any alternative ideas, given that the conditional local hook wouldn't work with React?

@gziolo
Copy link
Member

gziolo commented Mar 10, 2022

Given the discussion, what is your stance on this API now @gziolo? Do you have any alternative ideas, given that the conditional local hook wouldn't work with React?

I think my previous answer to the comment from @noisysocks is still valid:

Does the abstraction have to cover every use case? I'd say better to cover 80% of cases and keep using getMenuItems and hasFinishedResolution for cases like this.

This is exactly the same thought I have. While it addresses an edge case, the API is very confusing.

If you all agree that this API is going to be popular enough then feel free to introduce it. You definitely shouldn't hold the functionality proposed in this PR only because I very much dislike the limitations of React hooks which negatively impact developer experience. We at least tried to exercise some alternatives. 😄

One note, if you land this functionality for useEntityRecords, then you should also add it to useEntityRecord with the assumption that outside of WordPress core there might be similar cases for a single entity.

@adamziel
Copy link
Contributor Author

adamziel commented Mar 11, 2022

Does the abstraction have to cover every use case? I'd say better to cover 80% of cases and keep using getMenuItems and hasFinishedResolution for cases like this.

In my mind, this is within the boundaries of the 80% use case. Even across the Gutenberg codebase, we have a lot of these condition ? getEntityRecords() : [] lines:

return {
// Request only top-level comments. Replies are embedded.
topLevelComments: commentQuery
? getEntityRecords( 'root', 'comment', commentQuery )
: null,
blocks: getBlocks( clientId ),
};

const rawNavigationMenu = ref
? getEntityRecord( ...navigationMenuSingleArgs )
: null;
let navigationMenu = ref
? getEditedEntityRecord( ...navigationMenuSingleArgs )
: null;

if ( ! termIds.length ) {
return { isLoading: false };
}
const { getEntityRecords, isResolving } = select( coreStore );
const taxonomyArgs = [
'taxonomy',
slug,
{
include: termIds,
context: 'view',
},
];
const terms = getEntityRecords( ...taxonomyArgs );

if ( isTemplate ) {
const posts = getEntityRecords( 'postType', postType, {
wp_id: postId,
} );

reusableBlocks: ALLOW_REUSABLE_BLOCKS
? select( coreStore ).getEntityRecords( 'postType', 'wp_block' )
: [],

parentPost: pageId
? getEntityRecord( 'postType', postTypeSlug, pageId )
: null,
items: isHierarchical
? getEntityRecords( 'postType', postTypeSlug, query )
: [],

terms: _termIds.length
? getEntityRecords( 'taxonomy', slug, query )
: EMPTY_ARRAY,
hasResolvedTerms: hasFinishedResolution( 'getEntityRecords', [
'taxonomy',
slug,
query,
] ),

loading: isResolving( 'getEntityRecords', [
'taxonomy',
slug,
DEFAULT_QUERY,
] ),
availableTerms:
getEntityRecords( 'taxonomy', slug, DEFAULT_QUERY ) ||
EMPTY_ARRAY,

reusableBlocks: isWeb
? select( coreStore ).getEntityRecords(
'postType',
'wp_block',
{ per_page: -1 }
)
: [], // Reusable blocks are fetched in the native version of this hook.

If you inspect these files, these are often paired with isResolved. They're not included here since I didn't want to make this comment take up too much vertical space.

If you all agree that this API is going to be popular enough then feel free to introduce it. You definitely shouldn't hold the functionality proposed in this PR only because I very much dislike the limitations of React hooks which negatively impact developer experience. We at least tried to exercise some alternatives. 😄

@gziolo Oh yes, I would love to be able to just use an if and call it a day. Unfortunately, we can't, and so I think such an option is justified and fits well into the idea of useEntityRecord and useEntityRecords in the same way as react-query as an enabled option.

I did mark that option as an experimental API to make reverting easy in case it doesn't pan out somewhere down the road. I'd say let's try it and see how it goes.

@adamziel adamziel changed the title Add an "execute" option to useEntityRecords Add an "enabled" option to useEntityRecords Mar 11, 2022
@adamziel
Copy link
Contributor Author

It's used in react-query with the name enabled, which I think might be a better name than the verb execute.

Good idea! I just updated that name @kevin940726

@adamziel adamziel changed the title Add an "enabled" option to useEntityRecords Add an "enabled" option to useEntityRecord and useEntityRecords Mar 11, 2022
@adamziel
Copy link
Contributor Author

One note, if you land this functionality for useEntityRecords, then you should also add it to useEntityRecord with the assumption that outside of WordPress core there might be similar cases for a single entity.

👍 done!

@adamziel
Copy link
Contributor Author

Would you mind reviewing @getdave and @kevin940726 ?

Copy link
Member

@kevin940726 kevin940726 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Just some nitpicks/questions here and there.

}

interface Options {
__experimentalEnabled: boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want the option to be experimental? The hook itself is experimental, I don't think we have to experiment with it twice (inception noises) 😆 ?

[ kind, name, queryAsString ]
( query ) => {
if ( ! options.__experimentalEnabled ) {
return {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it expected to return an empty object rather than null here? It seems weird to me 🤔 , I'd imagine that it at least returns an empty array than an object.

Copy link
Contributor Author

@adamziel adamziel Mar 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this was null, then the destructuring would error. Returning an empty object makes the data undefined in line 86. Perhaps this could return an object like { data: [] }, but I don't think it matters that much – it's just a placeholder value for when the hook is disabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, reconsidering it – if you had to ask that question then it reads confusing. I turned it into

	if ( ! options.enabled ) {
		return {
			data: [],
		};
	}

If only to make it more readable.

@adamziel adamziel force-pushed the add/conditional-execution-to-use-entity-records branch 2 times, most recently from 7ab6409 to 28716cb Compare March 16, 2022 15:35
@adamziel adamziel force-pushed the add/conditional-execution-to-use-entity-records branch from 28716cb to 2cf3181 Compare March 16, 2022 16:23
@adamziel adamziel merged commit 9bad2ed into trunk Mar 16, 2022
@adamziel adamziel deleted the add/conditional-execution-to-use-entity-records branch March 16, 2022 18:17
@github-actions github-actions bot added this to the Gutenberg 12.9 milestone Mar 16, 2022
( query ) => {
if ( ! options.enabled ) {
return {
data: [],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably should const EMPTY_ARRAY, so a new reference isn't returned each time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot! Let's tackle it as a part of making this a public API

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Core data /packages/core-data [Type] Code Quality Issues or PRs that relate to code quality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants