Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mutations data and helper functions to useEntityRecord #39595

Merged
merged 12 commits into from
Aug 8, 2022

Conversation

adamziel
Copy link
Contributor

@adamziel adamziel commented Mar 19, 2022

What?

This PR proposes merging entity mutations utilities into the useEntityRecord hook. This is an alternative to #38135 and was earlier discussed in #39258.

The rationale is that manipulating entity records in core-data is quite difficult these days. Building a simple form currently requires a snippet like this:

const {
	editEntityRecord,
	saveEditedEntityRecord
} = useDispatch( coreStore );

const { editedRecord, hasEdits, isSaving, saveError } = useSelect(
	( select ) => {
		const args = [kind, type, id];
		return {
			editedRecord: select( coreStore ).getEditedEntityRecord( ...args ),
			hasEdits: select( coreStore ).hasEditsForEntityRecord( ...args ),
			isSaving: select( coreStore ).isSavingEntityRecord( ...args ),
			saveError: select( coreStore ).getLastEntitySaveError( ...args ),
		};
	},
	[kind, type, id],
);

I'd like it to look more like this:

const { record, edit, editedRecord, hasEdits, save } = useEntityRecord( 'postType', 'page', pageId );
// Or just:
const page = useEntityRecord( 'postType', 'page', pageId );
page.edit({ title: newTitle });
page.save();

Different angles

Re-throwing errors is different than having a convenience wrapper for actions like editEntityRecord, saveEditedEntityRecord, saveEntityRecord and the getEditedEntityRecord selector. Perhaps that's where a hook would actually shine:

const { create, isCreating, error } = useEntityRecordCreate();
const { editedRecord, applyEdits, saveEdits, isSaving, error } = useEntityRecordEdit();
const { remove, isRemoving, error } = useEntityRecordRemove(); // it's "remove", because "delete" is a reserved keyword

However, given the "throwing" actions, accessing error and isCreating/isSaving/isRemoving would not be needed as often, which leaves us with just:

const { create } = useEntityRecordCreate();
const { editedRecord, applyEdits, saveEdits } = useEntityRecordEdit();
const { remove } = useEntityRecordRemove();

Which doesn't do much for creating and removing records so we could also ditch these hooks and settle only for the useEntityRecord hook:

const { edit, editedRecord, hasEdits, save } = useEntityRecord( 'postType', 'page', pageId );

Dev note

The Make Core post can be easily repurposed as a dev note.

cc @gziolo

@adamziel adamziel added [Type] Code Quality Issues or PRs that relate to code quality [Package] Core data /packages/core-data labels Mar 19, 2022
@gziolo
Copy link
Member

gziolo commented Mar 25, 2022

Which doesn't do much for creating and removing records, so we could also ditch these hooks and settle only for the useEntityRecordEdit hook:

const { editedRecord, applyEdits, saveEdits } = useEntityRecordEdit();

I’m on board with the proposal. I totally agree with the reasoning shared.

In the diff of this PR, I see that it actually should be useEntityRecord function mentioned that gets new functionality 😃

Both approaches have pros and cons. When using two separate hooks, it might be easier to distinguish what’s the difference between record and editedRecord. On the other hand, using one hook makes it easier to handle the overall status of the record’s state.

I guess we would have to try some refactoring in Gutenberg with the current API to gather more information before drawing conclusions. Anyway, it’s great to see constant, interesting explorations in this area ❤️

@gziolo gziolo mentioned this pull request Mar 25, 2022
@adamziel adamziel force-pushed the hooks/entity-mutations-use-entity-record branch from 06f1c6d to 8501dc7 Compare April 4, 2022 12:48
@adamziel adamziel changed the title Add mutations data and helper functions to __experimentalUseEntityRecord Add mutations data and helper functions to useEntityRecord May 9, 2022
@adamziel adamziel force-pushed the hooks/entity-mutations-use-entity-record branch from 8501dc7 to e7276d5 Compare May 9, 2022 11:34
@github-actions
Copy link

github-actions bot commented May 9, 2022

Size Change: +940 B (0%)

Total Size: 1.27 MB

Filename Size Change
build/block-editor/index.min.js 154 kB +1 B (0%)
build/block-library/blocks/search/style-rtl.css 396 B +11 B (+3%)
build/block-library/blocks/search/style.css 393 B +7 B (+2%)
build/block-library/style-rtl.css 11.9 kB +3 B (0%)
build/block-library/style.css 11.9 kB +3 B (0%)
build/components/index.min.js 231 kB +64 B (0%)
build/components/style-rtl.css 14.1 kB -48 B (0%)
build/components/style.css 14.1 kB -49 B (0%)
build/core-data/index.min.js 14.8 kB +107 B (+1%)
build/edit-site/index.min.js 56.7 kB +822 B (+1%)
build/edit-site/style-rtl.css 8.23 kB +9 B (0%)
build/edit-site/style.css 8.21 kB +10 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 982 B
build/annotations/index.min.js 2.76 kB
build/api-fetch/index.min.js 2.26 kB
build/autop/index.min.js 2.14 kB
build/blob/index.min.js 475 B
build/block-directory/index.min.js 6.58 kB
build/block-directory/style-rtl.css 990 B
build/block-directory/style.css 991 B
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-editor/style-rtl.css 14.7 kB
build/block-editor/style.css 14.7 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 103 B
build/block-library/blocks/audio/style.css 103 B
build/block-library/blocks/audio/theme-rtl.css 110 B
build/block-library/blocks/audio/theme.css 110 B
build/block-library/blocks/avatar/editor-rtl.css 116 B
build/block-library/blocks/avatar/editor.css 116 B
build/block-library/blocks/avatar/style-rtl.css 59 B
build/block-library/blocks/avatar/style.css 59 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 441 B
build/block-library/blocks/button/editor.css 441 B
build/block-library/blocks/button/style-rtl.css 542 B
build/block-library/blocks/button/style.css 542 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-content/style-rtl.css 92 B
build/block-library/blocks/comment-content/style.css 92 B
build/block-library/blocks/comment-template/style-rtl.css 187 B
build/block-library/blocks/comment-template/style.css 185 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-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 834 B
build/block-library/blocks/comments/editor.css 832 B
build/block-library/blocks/comments/style-rtl.css 632 B
build/block-library/blocks/comments/style.css 630 B
build/block-library/blocks/cover/editor-rtl.css 615 B
build/block-library/blocks/cover/editor.css 616 B
build/block-library/blocks/cover/style-rtl.css 1.55 kB
build/block-library/blocks/cover/style.css 1.55 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 410 B
build/block-library/blocks/embed/style.css 410 B
build/block-library/blocks/embed/theme-rtl.css 110 B
build/block-library/blocks/embed/theme.css 110 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 253 B
build/block-library/blocks/file/style.css 254 B
build/block-library/blocks/file/view.min.js 346 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 948 B
build/block-library/blocks/gallery/editor.css 950 B
build/block-library/blocks/gallery/style-rtl.css 1.53 kB
build/block-library/blocks/gallery/style.css 1.53 kB
build/block-library/blocks/gallery/theme-rtl.css 108 B
build/block-library/blocks/gallery/theme.css 108 B
build/block-library/blocks/group/editor-rtl.css 333 B
build/block-library/blocks/group/editor.css 333 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 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/html/editor-rtl.css 327 B
build/block-library/blocks/html/editor.css 329 B
build/block-library/blocks/image/editor-rtl.css 736 B
build/block-library/blocks/image/editor.css 737 B
build/block-library/blocks/image/style-rtl.css 627 B
build/block-library/blocks/image/style.css 630 B
build/block-library/blocks/image/theme-rtl.css 110 B
build/block-library/blocks/image/theme.css 110 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 463 B
build/block-library/blocks/latest-posts/style.css 462 B
build/block-library/blocks/list/style-rtl.css 88 B
build/block-library/blocks/list/style.css 88 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 705 B
build/block-library/blocks/navigation-link/editor.css 703 B
build/block-library/blocks/navigation-link/style-rtl.css 115 B
build/block-library/blocks/navigation-link/style.css 115 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 296 B
build/block-library/blocks/navigation-submenu/editor.css 295 B
build/block-library/blocks/navigation-submenu/view.min.js 423 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.97 kB
build/block-library/blocks/navigation/style.css 1.96 kB
build/block-library/blocks/navigation/view-modal.min.js 2.78 kB
build/block-library/blocks/navigation/view.min.js 443 B
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 260 B
build/block-library/blocks/paragraph/style.css 260 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/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 493 B
build/block-library/blocks/post-comments-form/style.css 493 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 605 B
build/block-library/blocks/post-featured-image/editor.css 605 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 282 B
build/block-library/blocks/post-template/style.css 282 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 370 B
build/block-library/blocks/pullquote/style.css 370 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 282 B
build/block-library/blocks/query-pagination/style.css 278 B
build/block-library/blocks/query/editor-rtl.css 365 B
build/block-library/blocks/query/editor.css 364 B
build/block-library/blocks/quote/style-rtl.css 213 B
build/block-library/blocks/quote/style.css 213 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/theme-rtl.css 114 B
build/block-library/blocks/search/theme.css 114 B
build/block-library/blocks/separator/editor-rtl.css 146 B
build/block-library/blocks/separator/editor.css 146 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 194 B
build/block-library/blocks/separator/theme.css 194 B
build/block-library/blocks/shortcode/editor-rtl.css 464 B
build/block-library/blocks/shortcode/editor.css 464 B
build/block-library/blocks/site-logo/editor-rtl.css 708 B
build/block-library/blocks/site-logo/editor.css 708 B
build/block-library/blocks/site-logo/style-rtl.css 192 B
build/block-library/blocks/site-logo/style.css 192 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 184 B
build/block-library/blocks/social-link/editor.css 184 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.39 kB
build/block-library/blocks/social-links/style.css 1.38 kB
build/block-library/blocks/spacer/editor-rtl.css 322 B
build/block-library/blocks/spacer/editor.css 322 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 494 B
build/block-library/blocks/table/editor.css 494 B
build/block-library/blocks/table/style-rtl.css 611 B
build/block-library/blocks/table/style.css 609 B
build/block-library/blocks/table/theme-rtl.css 175 B
build/block-library/blocks/table/theme.css 175 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 561 B
build/block-library/blocks/video/editor.css 563 B
build/block-library/blocks/video/style-rtl.css 159 B
build/block-library/blocks/video/style.css 159 B
build/block-library/blocks/video/theme-rtl.css 110 B
build/block-library/blocks/video/theme.css 110 B
build/block-library/common-rtl.css 1.01 kB
build/block-library/common.css 1 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 10.9 kB
build/block-library/editor.css 10.9 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/index.min.js 185 kB
build/block-library/reset-rtl.css 478 B
build/block-library/reset.css 478 B
build/block-library/theme-rtl.css 695 B
build/block-library/theme.css 700 B
build/block-serialization-default-parser/index.min.js 1.11 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/blocks/index.min.js 47.3 kB
build/compose/index.min.js 11.7 kB
build/customize-widgets/index.min.js 11.3 kB
build/customize-widgets/style-rtl.css 1.4 kB
build/customize-widgets/style.css 1.4 kB
build/data-controls/index.min.js 653 B
build/data/index.min.js 8.03 kB
build/date/index.min.js 32 kB
build/deprecated/index.min.js 507 B
build/dom-ready/index.min.js 324 B
build/dom/index.min.js 4.69 kB
build/edit-navigation/index.min.js 16 kB
build/edit-navigation/style-rtl.css 4.02 kB
build/edit-navigation/style.css 4.03 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/index.min.js 30.5 kB
build/edit-post/style-rtl.css 6.94 kB
build/edit-post/style.css 6.94 kB
build/edit-widgets/index.min.js 16.5 kB
build/edit-widgets/style-rtl.css 4.35 kB
build/edit-widgets/style.css 4.35 kB
build/editor/index.min.js 41.3 kB
build/editor/style-rtl.css 3.66 kB
build/editor/style.css 3.65 kB
build/element/index.min.js 4.68 kB
build/escape-html/index.min.js 537 B
build/format-library/index.min.js 6.75 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.64 kB
build/html-entities/index.min.js 448 B
build/i18n/index.min.js 3.77 kB
build/is-shallow-equal/index.min.js 527 B
build/keyboard-shortcuts/index.min.js 1.78 kB
build/keycodes/index.min.js 1.39 kB
build/list-reusable-blocks/index.min.js 1.74 kB
build/list-reusable-blocks/style-rtl.css 835 B
build/list-reusable-blocks/style.css 835 B
build/media-utils/index.min.js 2.93 kB
build/notices/index.min.js 953 B
build/nux/index.min.js 2.05 kB
build/nux/style-rtl.css 732 B
build/nux/style.css 728 B
build/plugins/index.min.js 1.94 kB
build/preferences-persistence/index.min.js 2.22 kB
build/preferences/index.min.js 1.3 kB
build/primitives/index.min.js 933 B
build/priority-queue/index.min.js 612 B
build/react-i18n/index.min.js 696 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.74 kB
build/reusable-blocks/index.min.js 2.22 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.53 kB
build/token-list/index.min.js 644 B
build/url/index.min.js 3.61 kB
build/vendors/react-dom.min.js 38.5 kB
build/vendors/react.min.js 4.34 kB
build/viewport/index.min.js 1.08 kB
build/warning/index.min.js 268 B
build/widgets/index.min.js 7.19 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.06 kB

compressed-size-action

@adamziel
Copy link
Contributor Author

adamziel commented May 12, 2022

@gziolo interestingly, most usages of editEntityRecord, saveEditedEntityRecord, getEditedEntityRecord etc either live in actions or in highly optimized useSelect calls that I'd rather not refactor. I've just pushed a refactor of one component that fitted the profile of these changes well, but I don't see many other ones.

To me, that's a signal this PR is not as relevant in the Gutenberg repo at the moment. It could be still useful for extenders. What do you think?

edit: here's a few cross-searches I've ran to find files that match both e.g. editEntityRecord and saveEditedEntityRecord :

> comm -1 -2 <(git grep 'editEntityRecord' | awk -F ':' '{print $1}' | sort | uniq) <(git grep 'saveEditedEntityRecord' | awk -F ':' '{print $1}' | sort | uniq) | grep -v actions.js
changelog.txt
docs/how-to-guides/data-basics/3-building-an-edit-form.md
docs/reference-guides/data/data-core.md
packages/core-data/README.md
packages/core-data/src/hooks/use-entity-record.ts
packages/editor/src/components/entities-saved-states/index.js
> comm -1 -2 <(git grep 'getEditedEntityRecord' | awk -F ':' '{print $1}' | sort | uniq) <(git grep 'editEntityRecord' | awk -F ':' '{print $1}' | sort | uniq) | grep -v actions.js
docs/how-to-guides/data-basics/3-building-an-edit-form.md
docs/reference-guides/data/data-core.md
packages/block-library/src/post-author/edit.js
packages/block-library/src/site-logo/edit.js
packages/core-data/README.md
packages/core-data/src/entity-provider.js
packages/core-data/src/hooks/use-entity-record.ts
packages/edit-navigation/src/hooks/use-menu-entity.js
packages/edit-site/src/components/global-styles/global-styles-provider.js
> comm -1 -2 <(git grep 'getEditedEntityRecord' | awk -F ':' '{print $1}' | sort | uniq) <(git grep 'saveEntityRecord' | awk -F ':' '{print $1}' | sort | uniq) | grep -v actions.js                                                                                                     130 docs/reference-guides/data/data-core.md
packages/core-data/README.md

@gziolo
Copy link
Member

gziolo commented May 13, 2022

interestingly, most usages of editEntityRecord, saveEditedEntityRecord, getEditedEntityRecord etc either live in actions or in highly optimized useSelect calls that I'd rather not refactor. I've just pushed a refactor of one component that fitted the profile of these changes well, but I don't see many other ones.

I guess for Gutenberg in the majority of cases we call editEntityRecord in the block editor, and the saveEditedEntityRecord is handled only when saving the whole content (post/page/template). The only example you refactored looks nice, but it now makes me wonder why it's saved explicitly? 🤣

Now, the question would be: how do we get feedback from extenders about the API proposed?

@adamziel
Copy link
Contributor Author

Now, the question would be: how do we get feedback from extenders about the API proposed?

Great question! I wonder if @annezazu may have some insights here

@adamziel
Copy link
Contributor Author

For reference, here's how the EditPageForm component Building an edit form tutorial could change:

Without this PR merged:

const { page, lastError, isSaving, hasEdits } = useSelect(
    ( select ) => ( {
        page: select( coreDataStore ).getEditedEntityRecord( 'postType', 'page', pageId ),
        lastError: select( coreDataStore ).getLastEntitySaveError( 'postType', 'page', pageId ),
        isSaving: select( coreDataStore ).isSavingEntityRecord( 'postType', 'page', pageId ),
        hasEdits: select( coreDataStore ).hasEditsForEntityRecord( 'postType', 'page', pageId ),
    } ),
    [ pageId ]
);

const { saveEditedEntityRecord, editEntityRecord } = useDispatch( coreDataStore );
const handleSave = async () => {
    const savedRecord = await saveEditedEntityRecord( 'postType', 'page', pageId );
    if ( savedRecord ) {
        onSaveFinished();
    }
};
const handleChange = ( title ) =>  editEntityRecord( 'postType', 'page', page.id, { title } );

With this PR merged:

const { record, edit, save, lastError, isSaving, hasEdits } = useEntityRecord( 'postType', 'page', pageId );

const handleSave = async () => {
    const savedRecord = await save();
    if ( savedRecord ) {
        onSaveFinished();
    }
};
const handleChange = ( title ) =>  edit( { title } );

(note: lastError is missing from this PR at the moment)

@annezazu
Copy link
Contributor

What kind of feedback are you looking to get? :D Here are some ideas:

  • Could use WP Directory to do some searches followed by grassroots outreach to each plugin author to this PR: https://wpdirectory.net/search/01G3CMBWEV6N5750DNZV3EDMQ9
  • Can share in Core Editor meetings to lightly put it on people's radar.
  • Can write a post on Make Core if you're really trying to get a lot of attention!

Happy to help with any of the above/help coordinate.

@gziolo
Copy link
Member

gziolo commented May 19, 2022

@adamziel, the example from documentation you shared:

const { record, edit, save, lastError, isSaving, hasEdits } = useEntityRecord( 'postType', 'page', pageId );

const handleSave = async () => {
    const savedRecord = await save();
    if ( savedRecord ) {
        onSaveFinished();
    }
};
const handleChange = ( title ) =>  edit( { title } );

This looks great! We need more existing code like that to confirm we didn't miss anything. I agree we could use WP Slack and/or Twitter to find some plugin authors who might benefit from the API changes introduced. Great idea @annezazu!

Can write a post on Make Core if you're really trying to get a lot of attention!

That's also an option because @wordpress/core-data has already the stable version of useEntityRecord available on npm. So we could advertise it and ask folks whether this addition mets their needs and what else they would expect to see.

@adamziel
Copy link
Contributor Author

Thank you @annezazu and @gziolo !

I've run the following searches:

Here's what I found:

Plugins that could benefit from this PR:

Plugins that wouldn't benefit from this PR today:

The code samples I've analyzed follow the same pattern as I described in my last comment, e.g. select, dispatch, declare onSave callbacks and could be shortened/simplified using the updated useEntityRecord signature from this PR.

Now that we've established there's at least a few plugins that would benefit here, a Make Core post sounds like a reasonable next step.

As a side note, I wonder how much of a chicken&egg this is, e.g. people aren't using core-data utilities because they are not developer friendly and well documented.

@gziolo
Copy link
Member

gziolo commented Jun 14, 2022

As a side note, I wonder how much of a chicken&egg this is, e.g. people aren't using core-data utilities because they are not developer friendly and well documented.

That's a good point. It might be also a few other things like using @wordpress/core-data and the concept of entities for custom post types which might be still not supported. In Gutenberg, we have a legacy layer for handling the post/page post type inside @wordpress/editor package (see related issues to that in #32842) which makes it harder to benefit from all recent APIs.

Now that we've established there's at least a few plugins that would benefit here, a Make Core post sounds like a reasonable next step.

Let's do that step and see whether it helps with making the final call. At this point, my only concern is backward compatibility if we change our mind about the shape of the API a few months later. If that would be internal to the Gutenberg plugin I would be more than happy to ship it now.

@adamziel adamziel force-pushed the hooks/entity-mutations-use-entity-record branch from 713dbf9 to b019462 Compare August 4, 2022 09:13
packages/core-data/README.md Outdated Show resolved Hide resolved
packages/core-data/README.md Outdated Show resolved Hide resolved
@gziolo
Copy link
Member

gziolo commented Aug 4, 2022

The only important feedback I have is included in #39595 (comment). I was wondering whether we could optimize the usage of save with useEntityRecord if the recommended way is to use it with throwOnError flag enabled.


function PageRenameForm( { id } ) {
const page = useEntityRecord( 'postType', 'page', id );
const [ title, setTitle ] = useState( () => page.record.title.rendered );
Copy link
Member

Choose a reason for hiding this comment

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

One thing that still isn't clear to me. Why do we need useState here rather than using const title = page.editedRecord.title and const setTitle = ( value ) => page.edit( { title: value } )?

Copy link
Contributor Author

@adamziel adamziel Aug 8, 2022

Choose a reason for hiding this comment

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

const setTitle = ( value ) => page.edit( { title: value } ) would make undo work character by character :( I considered documenting that behavior in this code snippet and ended up leaving it out. There's already a lot of concepts in there.

Copy link
Contributor Author

@adamziel adamziel Aug 8, 2022

Choose a reason for hiding this comment

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

I'd love to go ahead and merge this PR since there are no conflicts and the checks are finally green :D we can tweak the docs in a follow-up if needed.

Copy link
Member

Choose a reason for hiding this comment

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

I approved the PR. I'm wondering how the undo/redo works with setAttributes from the block edit API. I don't see there an issue with storing all atomic operations when using onChange with TextControl.

Copy link
Member

Choose a reason for hiding this comment

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

I figured out why it works in a special way for block attributes. There is this util that checks whether the changes get applied to the same block's attribute:

/**
* Returns true if, given the currently dispatching action and the previously
* dispatched action, the two actions are updating the same block attribute, or
* false otherwise.
*
* @param {Object} action Currently dispatching action.
* @param {Object} lastAction Previously dispatched action.
*
* @return {boolean} Whether actions are updating the same block attribute.
*/
export function isUpdatingSameBlockAttribute( action, lastAction ) {
return (
action.type === 'UPDATE_BLOCK_ATTRIBUTES' &&
lastAction !== undefined &&
lastAction.type === 'UPDATE_BLOCK_ATTRIBUTES' &&
isEqual( action.clientIds, lastAction.clientIds ) &&
hasSameKeys( action.attributes, lastAction.attributes )
);
}

It's used by the higher-order reducer that ensures that only a single entry gets added to the under/redo state for subsequent changes to the same attribute:

/**
* Higher-order reducer intended to augment the blocks reducer, assigning an
* `isPersistentChange` property value corresponding to whether a change in
* state can be considered as persistent. All changes are considered persistent
* except when updating the same block attribute as in the previous action.
*
* @param {Function} reducer Original reducer function.
*
* @return {Function} Enhanced reducer function.
*/
function withPersistentBlockChange( reducer ) {
let lastAction;
let markNextChangeAsNotPersistent = false;
return ( state, action ) => {
let nextState = reducer( state, action );
const isExplicitPersistentChange =
action.type === 'MARK_LAST_CHANGE_AS_PERSISTENT' ||
markNextChangeAsNotPersistent;
// Defer to previous state value (or default) unless changing or
// explicitly marking as persistent.
if ( state === nextState && ! isExplicitPersistentChange ) {
markNextChangeAsNotPersistent =
action.type === 'MARK_NEXT_CHANGE_AS_NOT_PERSISTENT';
const nextIsPersistentChange = state?.isPersistentChange ?? true;
if ( state.isPersistentChange === nextIsPersistentChange ) {
return state;
}
return {
...nextState,
isPersistentChange: nextIsPersistentChange,
};
}
nextState = {
...nextState,
isPersistentChange: isExplicitPersistentChange
? ! markNextChangeAsNotPersistent
: ! isUpdatingSameBlockAttribute( action, lastAction ),
};
// In comparing against the previous action, consider only those which
// would have qualified as one which would have been ignored or not
// have resulted in a changed state.
lastAction = action;
markNextChangeAsNotPersistent =
action.type === 'MARK_NEXT_CHANGE_AS_NOT_PERSISTENT';
return nextState;
};
}

Maybe we should figure out how to mirror the same behavior for entities to unify the experience and bring more value to using editedRecord and the edit method from this hook.

Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

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

Nice work extending the API for useEntityRecord. I find it as a very good improvement over what we had before.

It feels like the underlying concepts like records vs editedRecords and edits vs save still need some improved documentation as the included code example doesn't fully present what are the best practices for using them.

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.

3 participants