From 5852b89d5c72983e4fe00a6a7f5e8d2a79991e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Mon, 7 Mar 2022 16:42:22 +0100 Subject: [PATCH 01/14] Propose hooks for creating, deleting, and updating entity records alongside usage examples --- .../src/hooks/use-entity-record-create.ts | 30 ++++++++++++++++ .../src/hooks/use-entity-record-delete.ts | 30 ++++++++++++++++ .../src/hooks/use-entity-record-update.ts | 33 +++++++++++++++++ packages/core-data/src/index.js | 3 ++ .../header/template-title/delete-template.js | 11 ++++-- .../add-new-template/new-template-part.js | 32 +++++------------ .../add-new-template/new-template.js | 35 +++++++------------ .../list/actions/rename-menu-item.js | 29 +++++---------- 8 files changed, 133 insertions(+), 70 deletions(-) create mode 100644 packages/core-data/src/hooks/use-entity-record-create.ts create mode 100644 packages/core-data/src/hooks/use-entity-record-delete.ts create mode 100644 packages/core-data/src/hooks/use-entity-record-update.ts diff --git a/packages/core-data/src/hooks/use-entity-record-create.ts b/packages/core-data/src/hooks/use-entity-record-create.ts new file mode 100644 index 00000000000000..bb7fc97acc0915 --- /dev/null +++ b/packages/core-data/src/hooks/use-entity-record-create.ts @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { store as coreStore } from '../'; + +export default function useEntityRecordCreate( kind, type ) { + const { saveEntityRecord } = useDispatch( coreStore ); + const { getLastEntitySaveError } = useSelect( coreStore ); + + return useMemo( + () => ( { + create: async ( record ) => { + const result = await saveEntityRecord( kind, type, record ); + const error = getLastEntitySaveError( kind, type ); + // Error may be null, but the result is only available if everything worked correctly. + if ( ! result ) { + throw error; + } + return result; + }, + } ), + [ kind, type ] + ); +} diff --git a/packages/core-data/src/hooks/use-entity-record-delete.ts b/packages/core-data/src/hooks/use-entity-record-delete.ts new file mode 100644 index 00000000000000..8ba8533e88d9aa --- /dev/null +++ b/packages/core-data/src/hooks/use-entity-record-delete.ts @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { store as coreStore } from '../'; + +export default function useEntityRecordDelete( kind, type, id ) { + const { deleteEntityRecord } = useDispatch( coreStore ); + const { getLastEntityDeleteError } = useSelect( coreStore ); + + return useMemo( + () => ( { + deleteRecord: async () => { + const result = await deleteEntityRecord( kind, type, id ); + const error = getLastEntityDeleteError( kind, type, id ); + // Error may be null, but the result is only available if everything worked correctly. + if ( ! result ) { + throw error; + } + return result; + }, + } ), + [ kind, type, id ] + ); +} diff --git a/packages/core-data/src/hooks/use-entity-record-update.ts b/packages/core-data/src/hooks/use-entity-record-update.ts new file mode 100644 index 00000000000000..88ea091cd5c026 --- /dev/null +++ b/packages/core-data/src/hooks/use-entity-record-update.ts @@ -0,0 +1,33 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { store as coreStore } from '../'; + +export default function useEntityRecordUpdate( kind, type, id ) { + const { editEntityRecord, saveEditedEntityRecord } = useDispatch( + coreStore + ); + const { getLastEntitySaveError } = useSelect( coreStore ); + return useMemo( + () => ( { + applyEdits: ( record ) => + editEntityRecord( kind, type, id, record ), + saveEdits: async () => { + const result = await saveEditedEntityRecord( kind, type, id ); + const error = getLastEntitySaveError( kind, type, id ); + // Error may be null, but the result is only available if everything worked correctly. + if ( ! result ) { + throw error; + } + return result; + }, + } ), + [ kind, type, id ] + ); +} diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index ebf616c5a0d575..d132189379ad35 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -75,6 +75,9 @@ register( store ); export { default as EntityProvider } from './entity-provider'; export { default as __experimentalUseEntityRecord } from './hooks/use-entity-record'; export { default as __experimentalUseEntityRecords } from './hooks/use-entity-records'; +export { default as __experimentalUseEntityRecordUpdate } from './hooks/use-entity-record-update'; +export { default as __experimentalUseEntityRecordCreate } from './hooks/use-entity-record-create'; +export { default as __experimentalUseEntityRecordDelete } from './hooks/use-entity-record-delete'; export * from './entity-provider'; export * from './fetch'; export * from './entity-types'; diff --git a/packages/edit-post/src/components/header/template-title/delete-template.js b/packages/edit-post/src/components/header/template-title/delete-template.js index 3d09c4e34caf34..6375dc47e73355 100644 --- a/packages/edit-post/src/components/header/template-title/delete-template.js +++ b/packages/edit-post/src/components/header/template-title/delete-template.js @@ -15,7 +15,8 @@ import { import { store as blockEditorStore } from '@wordpress/block-editor'; import { useDispatch, useSelect } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; -import { store as coreStore } from '@wordpress/core-data'; +import { __experimentalUseEntityRecordDelete as useEntityRecordDelete } from '@wordpress/core-data'; + import { useState } from '@wordpress/element'; /** @@ -28,7 +29,11 @@ export default function DeleteTemplate() { const { setIsEditingTemplate } = useDispatch( editPostStore ); const { getEditorSettings } = useSelect( editorStore ); const { updateEditorSettings, editPost } = useDispatch( editorStore ); - const { deleteEntityRecord } = useDispatch( coreStore ); + const { deleteRecord } = useEntityRecordDelete( + 'postType', + 'wp_template', + template.id + ); const { template } = useSelect( ( select ) => { const { isEditingTemplate, getEditedPostTemplate } = select( editPostStore @@ -67,7 +72,7 @@ export default function DeleteTemplate() { ...settings, availableTemplates: newAvailableTemplates, } ); - deleteEntityRecord( 'postType', 'wp_template', template.id ); + deleteRecord(); }; return ( diff --git a/packages/edit-site/src/components/add-new-template/new-template-part.js b/packages/edit-site/src/components/add-new-template/new-template-part.js index 379ae5f8854a41..0d5a0cfb376b94 100644 --- a/packages/edit-site/src/components/add-new-template/new-template-part.js +++ b/packages/edit-site/src/components/add-new-template/new-template-part.js @@ -7,11 +7,11 @@ import { kebabCase } from 'lodash'; * WordPress dependencies */ import { useState } from '@wordpress/element'; -import { useSelect, useDispatch } from '@wordpress/data'; +import { useDispatch } from '@wordpress/data'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; -import { store as coreStore } from '@wordpress/core-data'; +import { __experimentalUseEntityRecordCreate as useEntityRecordCreate } from '@wordpress/core-data'; /** * Internal dependencies @@ -23,8 +23,7 @@ export default function NewTemplatePart( { postType } ) { const history = useHistory(); const [ isModalOpen, setIsModalOpen ] = useState( false ); const { createErrorNotice } = useDispatch( noticesStore ); - const { saveEntityRecord } = useDispatch( coreStore ); - const { getLastEntitySaveError } = useSelect( coreStore ); + const { create } = useEntityRecordCreate( 'postType', 'wp_template_part' ); async function createTemplatePart( { title, area } ) { if ( ! title ) { @@ -41,25 +40,12 @@ export default function NewTemplatePart( { postType } ) { kebabCase( title ).replace( /[^\w-]+/g, '' ) || 'wp-custom-part'; - const templatePart = await saveEntityRecord( - 'postType', - 'wp_template_part', - { - slug: cleanSlug, - title, - content: '', - area, - } - ); - - const lastEntitySaveError = getLastEntitySaveError( - 'postType', - 'wp_template_part', - templatePart.id - ); - if ( lastEntitySaveError ) { - throw lastEntitySaveError; - } + const templatePart = await create( { + slug: cleanSlug, + title, + content: '', + area, + } ); setIsModalOpen( false ); diff --git a/packages/edit-site/src/components/add-new-template/new-template.js b/packages/edit-site/src/components/add-new-template/new-template.js index 9f3328472eb7e6..18ecfe8a436453 100644 --- a/packages/edit-site/src/components/add-new-template/new-template.js +++ b/packages/edit-site/src/components/add-new-template/new-template.js @@ -13,7 +13,10 @@ import { NavigableMenu, } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; -import { store as coreStore } from '@wordpress/core-data'; +import { + store as coreStore, + __experimentalUseEntityRecordCreate as useEntityRecordCreate, +} from '@wordpress/core-data'; import { store as editorStore } from '@wordpress/editor'; import { archive, @@ -84,9 +87,8 @@ export default function NewTemplate( { postType } ) { } ), [] ); - const { saveEntityRecord } = useDispatch( coreStore ); const { createErrorNotice } = useDispatch( noticesStore ); - const { getLastEntitySaveError } = useSelect( coreStore ); + const { create } = useEntityRecordCreate( 'postType', 'wp_template' ); async function createTemplate( { slug } ) { try { @@ -94,26 +96,13 @@ export default function NewTemplate( { postType } ) { slug, } ); - const template = await saveEntityRecord( - 'postType', - 'wp_template', - { - excerpt: description, - // Slugs need to be strings, so this is for template `404` - slug: slug.toString(), - status: 'publish', - title, - } - ); - - const lastEntitySaveError = getLastEntitySaveError( - 'postType', - 'wp_template', - template.id - ); - if ( lastEntitySaveError ) { - throw lastEntitySaveError; - } + const template = await create( { + excerpt: description, + // Slugs need to be strings, so this is for template `404` + slug: slug.toString(), + status: 'publish', + title, + } ); // Navigate to the created template editor. history.push( { diff --git a/packages/edit-site/src/components/list/actions/rename-menu-item.js b/packages/edit-site/src/components/list/actions/rename-menu-item.js index 4eb7d1691ad709..962a25b569eed3 100644 --- a/packages/edit-site/src/components/list/actions/rename-menu-item.js +++ b/packages/edit-site/src/components/list/actions/rename-menu-item.js @@ -3,7 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { useState } from '@wordpress/element'; -import { useDispatch, useSelect } from '@wordpress/data'; +import { useDispatch } from '@wordpress/data'; import { Button, Flex, @@ -12,16 +12,17 @@ import { Modal, TextControl, } from '@wordpress/components'; -import { store as coreStore } from '@wordpress/core-data'; +import { __experimentalUseEntityRecordUpdate as useEntityRecordUpdate } from '@wordpress/core-data'; import { store as noticesStore } from '@wordpress/notices'; export default function RenameMenuItem( { template, onClose } ) { const [ title, setTitle ] = useState( () => template.title.rendered ); const [ isModalOpen, setIsModalOpen ] = useState( false ); - const { getLastEntitySaveError } = useSelect( coreStore ); - const { editEntityRecord, saveEditedEntityRecord } = useDispatch( - coreStore + const { applyEdits, saveEdits } = useEntityRecordUpdate( + 'postType', + template.type, + template.id ); const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore @@ -35,7 +36,7 @@ export default function RenameMenuItem( { template, onClose } ) { event.preventDefault(); try { - await editEntityRecord( 'postType', template.type, template.id, { + await applyEdits( { title, } ); @@ -45,21 +46,7 @@ export default function RenameMenuItem( { template, onClose } ) { onClose(); // Persist edited entity. - await saveEditedEntityRecord( - 'postType', - template.type, - template.id - ); - - const lastError = getLastEntitySaveError( - 'postType', - template.type, - template.id - ); - - if ( lastError ) { - throw lastError; - } + await saveEdits(); createSuccessNotice( __( 'Entity renamed.' ), { type: 'snackbar', From 8f31d6557eada26f1d390b618667909f7b31edd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 8 Mar 2022 14:23:24 +0100 Subject: [PATCH 02/14] Check for error before throwing --- packages/core-data/src/hooks/use-entity-record-create.ts | 3 +-- packages/core-data/src/hooks/use-entity-record-delete.ts | 3 +-- packages/core-data/src/hooks/use-entity-record-update.ts | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/core-data/src/hooks/use-entity-record-create.ts b/packages/core-data/src/hooks/use-entity-record-create.ts index bb7fc97acc0915..d6306d8073fa05 100644 --- a/packages/core-data/src/hooks/use-entity-record-create.ts +++ b/packages/core-data/src/hooks/use-entity-record-create.ts @@ -18,8 +18,7 @@ export default function useEntityRecordCreate( kind, type ) { create: async ( record ) => { const result = await saveEntityRecord( kind, type, record ); const error = getLastEntitySaveError( kind, type ); - // Error may be null, but the result is only available if everything worked correctly. - if ( ! result ) { + if ( error || ! result ) { throw error; } return result; diff --git a/packages/core-data/src/hooks/use-entity-record-delete.ts b/packages/core-data/src/hooks/use-entity-record-delete.ts index 8ba8533e88d9aa..34d2dee71de0ce 100644 --- a/packages/core-data/src/hooks/use-entity-record-delete.ts +++ b/packages/core-data/src/hooks/use-entity-record-delete.ts @@ -18,8 +18,7 @@ export default function useEntityRecordDelete( kind, type, id ) { deleteRecord: async () => { const result = await deleteEntityRecord( kind, type, id ); const error = getLastEntityDeleteError( kind, type, id ); - // Error may be null, but the result is only available if everything worked correctly. - if ( ! result ) { + if ( error || ! result ) { throw error; } return result; diff --git a/packages/core-data/src/hooks/use-entity-record-update.ts b/packages/core-data/src/hooks/use-entity-record-update.ts index 88ea091cd5c026..312d522219749a 100644 --- a/packages/core-data/src/hooks/use-entity-record-update.ts +++ b/packages/core-data/src/hooks/use-entity-record-update.ts @@ -21,8 +21,7 @@ export default function useEntityRecordUpdate( kind, type, id ) { saveEdits: async () => { const result = await saveEditedEntityRecord( kind, type, id ); const error = getLastEntitySaveError( kind, type, id ); - // Error may be null, but the result is only available if everything worked correctly. - if ( ! result ) { + if ( error || ! result ) { throw error; } return result; From 241fa50bf29af885be6ceb72436c1706e31898bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 8 Mar 2022 14:35:22 +0100 Subject: [PATCH 03/14] Try the "throwing actions" approach --- docs/reference-guides/data/data-core.md | 12 ++++ packages/core-data/README.md | 12 ++++ packages/core-data/src/actions.js | 66 +++++++++++++++++++ .../src/hooks/use-entity-record-create.ts | 29 -------- .../src/hooks/use-entity-record-delete.ts | 29 -------- .../src/hooks/use-entity-record-update.ts | 32 --------- .../header/template-title/delete-template.js | 11 +--- .../add-new-template/new-template-part.js | 20 +++--- .../add-new-template/new-template.js | 25 +++---- .../list/actions/rename-menu-item.js | 16 +++-- 10 files changed, 127 insertions(+), 125 deletions(-) delete mode 100644 packages/core-data/src/hooks/use-entity-record-create.ts delete mode 100644 packages/core-data/src/hooks/use-entity-record-delete.ts delete mode 100644 packages/core-data/src/hooks/use-entity-record-update.ts diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index f54e541122e150..3aff1d1b221340 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -735,6 +735,18 @@ _Parameters_ - _options.isAutosave_ `[boolean]`: Whether this is an autosave. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. +### throwingDeleteEntityRecord + +Undocumented declaration. + +### throwingSaveEditedEntityRecord + +Undocumented declaration. + +### throwingSaveEntityRecord + +Undocumented declaration. + ### undo Action triggered to undo the last edit to diff --git a/packages/core-data/README.md b/packages/core-data/README.md index 4b9d4536764fa3..c9fed73ae0e6c0 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -238,6 +238,18 @@ _Parameters_ - _options.isAutosave_ `[boolean]`: Whether this is an autosave. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. +### throwingDeleteEntityRecord + +Undocumented declaration. + +### throwingSaveEditedEntityRecord + +Undocumented declaration. + +### throwingSaveEntityRecord + +Undocumented declaration. + ### undo Action triggered to undo the last edit to diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index cd8fed52937c03..a657251cbda64b 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -759,3 +759,69 @@ export function receiveAutosaves( postId, autosaves ) { autosaves: castArray( autosaves ), }; } + +export const throwingSaveEntityRecord = ( ...args ) => async ( { + dispatch, + select, +} ) => { + const [ kind, name, record ] = args; + const recordId = await dispatch( getRecordPk( kind, name, record ) ); + + const result = await dispatch( saveEntityRecord( ...args ) ); + if ( ! result ) { + if ( recordId ) { + const error = select.getLastEntitySaveError( kind, name, recordId ); + throw error; + } else { + throw new Error( 'Something went wrong' ); + } + } + + const resultId = await dispatch( getRecordPk( kind, name, result ) ); + const error = select.getLastEntitySaveError( kind, name, resultId ); + + if ( error ) { + throw error; + } + return result; +}; + +export const throwingSaveEditedEntityRecord = ( ...args ) => async ( { + dispatch, + select, +} ) => { + const result = await dispatch( saveEditedEntityRecord( ...args ) ); + + const [ kind, name, id ] = args; + const error = select.getLastEntitySaveError( kind, name, id ); + + if ( error || ! result ) { + throw error; + } + return result; +}; + +export const throwingDeleteEntityRecord = ( ...args ) => async ( { + dispatch, + select, +} ) => { + const result = await dispatch( deleteEntityRecord( ...args ) ); + + const [ kind, name, id ] = args; + const error = select.getLastEntityDeleteError( kind, name, id ); + + if ( error || ! result ) { + throw error; + } + return result; +}; + +const getRecordPk = ( kind, name, record ) => async ( { dispatch } ) => { + const entities = await dispatch( getKindEntities( kind ) ); + const entity = find( entities, { kind, name } ); + if ( ! entity || entity?.__experimentalNoFetch ) { + return; + } + const entityIdKey = entity.key || DEFAULT_ENTITY_KEY; + return record[ entityIdKey ]; +}; diff --git a/packages/core-data/src/hooks/use-entity-record-create.ts b/packages/core-data/src/hooks/use-entity-record-create.ts deleted file mode 100644 index d6306d8073fa05..00000000000000 --- a/packages/core-data/src/hooks/use-entity-record-create.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * WordPress dependencies - */ -import { useMemo } from '@wordpress/element'; -import { useDispatch, useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { store as coreStore } from '../'; - -export default function useEntityRecordCreate( kind, type ) { - const { saveEntityRecord } = useDispatch( coreStore ); - const { getLastEntitySaveError } = useSelect( coreStore ); - - return useMemo( - () => ( { - create: async ( record ) => { - const result = await saveEntityRecord( kind, type, record ); - const error = getLastEntitySaveError( kind, type ); - if ( error || ! result ) { - throw error; - } - return result; - }, - } ), - [ kind, type ] - ); -} diff --git a/packages/core-data/src/hooks/use-entity-record-delete.ts b/packages/core-data/src/hooks/use-entity-record-delete.ts deleted file mode 100644 index 34d2dee71de0ce..00000000000000 --- a/packages/core-data/src/hooks/use-entity-record-delete.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * WordPress dependencies - */ -import { useMemo } from '@wordpress/element'; -import { useDispatch, useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { store as coreStore } from '../'; - -export default function useEntityRecordDelete( kind, type, id ) { - const { deleteEntityRecord } = useDispatch( coreStore ); - const { getLastEntityDeleteError } = useSelect( coreStore ); - - return useMemo( - () => ( { - deleteRecord: async () => { - const result = await deleteEntityRecord( kind, type, id ); - const error = getLastEntityDeleteError( kind, type, id ); - if ( error || ! result ) { - throw error; - } - return result; - }, - } ), - [ kind, type, id ] - ); -} diff --git a/packages/core-data/src/hooks/use-entity-record-update.ts b/packages/core-data/src/hooks/use-entity-record-update.ts deleted file mode 100644 index 312d522219749a..00000000000000 --- a/packages/core-data/src/hooks/use-entity-record-update.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * WordPress dependencies - */ -import { useMemo } from '@wordpress/element'; -import { useDispatch, useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { store as coreStore } from '../'; - -export default function useEntityRecordUpdate( kind, type, id ) { - const { editEntityRecord, saveEditedEntityRecord } = useDispatch( - coreStore - ); - const { getLastEntitySaveError } = useSelect( coreStore ); - return useMemo( - () => ( { - applyEdits: ( record ) => - editEntityRecord( kind, type, id, record ), - saveEdits: async () => { - const result = await saveEditedEntityRecord( kind, type, id ); - const error = getLastEntitySaveError( kind, type, id ); - if ( error || ! result ) { - throw error; - } - return result; - }, - } ), - [ kind, type, id ] - ); -} diff --git a/packages/edit-post/src/components/header/template-title/delete-template.js b/packages/edit-post/src/components/header/template-title/delete-template.js index 6375dc47e73355..26283fc71e1bc7 100644 --- a/packages/edit-post/src/components/header/template-title/delete-template.js +++ b/packages/edit-post/src/components/header/template-title/delete-template.js @@ -15,8 +15,7 @@ import { import { store as blockEditorStore } from '@wordpress/block-editor'; import { useDispatch, useSelect } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; -import { __experimentalUseEntityRecordDelete as useEntityRecordDelete } from '@wordpress/core-data'; - +import { store as coreStore } from '@wordpress/core-data'; import { useState } from '@wordpress/element'; /** @@ -29,11 +28,7 @@ export default function DeleteTemplate() { const { setIsEditingTemplate } = useDispatch( editPostStore ); const { getEditorSettings } = useSelect( editorStore ); const { updateEditorSettings, editPost } = useDispatch( editorStore ); - const { deleteRecord } = useEntityRecordDelete( - 'postType', - 'wp_template', - template.id - ); + const { throwingDeleteEntityRecord } = useDispatch( coreStore ); const { template } = useSelect( ( select ) => { const { isEditingTemplate, getEditedPostTemplate } = select( editPostStore @@ -72,7 +67,7 @@ export default function DeleteTemplate() { ...settings, availableTemplates: newAvailableTemplates, } ); - deleteRecord(); + throwingDeleteEntityRecord( 'postType', 'wp_template', template.id ); }; return ( diff --git a/packages/edit-site/src/components/add-new-template/new-template-part.js b/packages/edit-site/src/components/add-new-template/new-template-part.js index 0d5a0cfb376b94..032b5716bc12cf 100644 --- a/packages/edit-site/src/components/add-new-template/new-template-part.js +++ b/packages/edit-site/src/components/add-new-template/new-template-part.js @@ -11,7 +11,7 @@ import { useDispatch } from '@wordpress/data'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; -import { __experimentalUseEntityRecordCreate as useEntityRecordCreate } from '@wordpress/core-data'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -23,7 +23,7 @@ export default function NewTemplatePart( { postType } ) { const history = useHistory(); const [ isModalOpen, setIsModalOpen ] = useState( false ); const { createErrorNotice } = useDispatch( noticesStore ); - const { create } = useEntityRecordCreate( 'postType', 'wp_template_part' ); + const { throwingSaveEntityRecord } = useDispatch( coreStore ); async function createTemplatePart( { title, area } ) { if ( ! title ) { @@ -40,12 +40,16 @@ export default function NewTemplatePart( { postType } ) { kebabCase( title ).replace( /[^\w-]+/g, '' ) || 'wp-custom-part'; - const templatePart = await create( { - slug: cleanSlug, - title, - content: '', - area, - } ); + const templatePart = await throwingSaveEntityRecord( + 'postType', + 'wp_template_part', + { + slug: cleanSlug, + title, + content: '', + area, + } + ); setIsModalOpen( false ); diff --git a/packages/edit-site/src/components/add-new-template/new-template.js b/packages/edit-site/src/components/add-new-template/new-template.js index 18ecfe8a436453..610d01207ff524 100644 --- a/packages/edit-site/src/components/add-new-template/new-template.js +++ b/packages/edit-site/src/components/add-new-template/new-template.js @@ -13,10 +13,7 @@ import { NavigableMenu, } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; -import { - store as coreStore, - __experimentalUseEntityRecordCreate as useEntityRecordCreate, -} from '@wordpress/core-data'; +import { store as coreStore } from '@wordpress/core-data'; import { store as editorStore } from '@wordpress/editor'; import { archive, @@ -87,8 +84,8 @@ export default function NewTemplate( { postType } ) { } ), [] ); + const { throwingSaveEntityRecord } = useDispatch( coreStore ); const { createErrorNotice } = useDispatch( noticesStore ); - const { create } = useEntityRecordCreate( 'postType', 'wp_template' ); async function createTemplate( { slug } ) { try { @@ -96,13 +93,17 @@ export default function NewTemplate( { postType } ) { slug, } ); - const template = await create( { - excerpt: description, - // Slugs need to be strings, so this is for template `404` - slug: slug.toString(), - status: 'publish', - title, - } ); + const template = await throwingSaveEntityRecord( + 'postType', + 'wp_template', + { + excerpt: description, + // Slugs need to be strings, so this is for template `404` + slug: slug.toString(), + status: 'publish', + title, + } + ); // Navigate to the created template editor. history.push( { diff --git a/packages/edit-site/src/components/list/actions/rename-menu-item.js b/packages/edit-site/src/components/list/actions/rename-menu-item.js index 962a25b569eed3..219515bceaf815 100644 --- a/packages/edit-site/src/components/list/actions/rename-menu-item.js +++ b/packages/edit-site/src/components/list/actions/rename-menu-item.js @@ -12,17 +12,15 @@ import { Modal, TextControl, } from '@wordpress/components'; -import { __experimentalUseEntityRecordUpdate as useEntityRecordUpdate } from '@wordpress/core-data'; +import { store as coreStore } from '@wordpress/core-data'; import { store as noticesStore } from '@wordpress/notices'; export default function RenameMenuItem( { template, onClose } ) { const [ title, setTitle ] = useState( () => template.title.rendered ); const [ isModalOpen, setIsModalOpen ] = useState( false ); - const { applyEdits, saveEdits } = useEntityRecordUpdate( - 'postType', - template.type, - template.id + const { editEntityRecord, throwingSaveEditedEntityRecord } = useDispatch( + coreStore ); const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore @@ -36,7 +34,7 @@ export default function RenameMenuItem( { template, onClose } ) { event.preventDefault(); try { - await applyEdits( { + await editEntityRecord( 'postType', template.type, template.id, { title, } ); @@ -46,7 +44,11 @@ export default function RenameMenuItem( { template, onClose } ) { onClose(); // Persist edited entity. - await saveEdits(); + await throwingSaveEditedEntityRecord( + 'postType', + template.type, + template.id + ); createSuccessNotice( __( 'Entity renamed.' ), { type: 'snackbar', From e58f63225404d3102978c27dc37c24840e23630d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 8 Mar 2022 14:37:31 +0100 Subject: [PATCH 04/14] Use throwingSaveEditedEntityRecord in widgets and navigation stores --- packages/edit-navigation/src/store/actions.js | 10 +--------- packages/edit-widgets/src/store/actions.js | 15 +++++---------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/packages/edit-navigation/src/store/actions.js b/packages/edit-navigation/src/store/actions.js index beed4b75d50ef4..13946bd9edd98b 100644 --- a/packages/edit-navigation/src/store/actions.js +++ b/packages/edit-navigation/src/store/actions.js @@ -56,15 +56,7 @@ export const saveNavigationPost = ( post ) => async ( { // Save menu. await registry .dispatch( coreDataStore ) - .saveEditedEntityRecord( 'root', 'menu', menuId ); - - const error = registry - .select( coreDataStore ) - .getLastEntitySaveError( 'root', 'menu', menuId ); - - if ( error ) { - throw new Error( error.message ); - } + .throwingSaveEditedEntityRecord( 'root', 'menu', menuId ); // Save menu items. const updatedBlocks = await dispatch( diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js index 1f57b042a8932c..76dc87f090df90 100644 --- a/packages/edit-widgets/src/store/actions.js +++ b/packages/edit-widgets/src/store/actions.js @@ -282,18 +282,13 @@ export const saveWidgetArea = ( widgetAreaId ) => async ( { }; const trySaveWidgetArea = ( widgetAreaId ) => ( { registry } ) => { - const saveErrorBefore = registry - .select( coreStore ) - .getLastEntitySaveError( KIND, WIDGET_AREA_ENTITY_TYPE, widgetAreaId ); registry .dispatch( coreStore ) - .saveEditedEntityRecord( KIND, WIDGET_AREA_ENTITY_TYPE, widgetAreaId ); - const saveErrorAfter = registry - .select( coreStore ) - .getLastEntitySaveError( KIND, WIDGET_AREA_ENTITY_TYPE, widgetAreaId ); - if ( saveErrorAfter && saveErrorBefore !== saveErrorAfter ) { - throw new Error( saveErrorAfter ); - } + .throwingSaveEditedEntityRecord( + KIND, + WIDGET_AREA_ENTITY_TYPE, + widgetAreaId + ); }; /** From 0ffd8da686ae83b353d4c02ae46a6a080792c4a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 8 Mar 2022 14:38:22 +0100 Subject: [PATCH 05/14] Remove hooks exports --- packages/core-data/src/index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index d132189379ad35..ebf616c5a0d575 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -75,9 +75,6 @@ register( store ); export { default as EntityProvider } from './entity-provider'; export { default as __experimentalUseEntityRecord } from './hooks/use-entity-record'; export { default as __experimentalUseEntityRecords } from './hooks/use-entity-records'; -export { default as __experimentalUseEntityRecordUpdate } from './hooks/use-entity-record-update'; -export { default as __experimentalUseEntityRecordCreate } from './hooks/use-entity-record-create'; -export { default as __experimentalUseEntityRecordDelete } from './hooks/use-entity-record-delete'; export * from './entity-provider'; export * from './fetch'; export * from './entity-types'; From 90eac4af3f7c6e1f2516f8729a64909208392995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 11 Mar 2022 11:00:18 +0100 Subject: [PATCH 06/14] Remove the "throwing" actions in favor of a throwOnError option --- docs/reference-guides/data/data-core.md | 14 +-- packages/core-data/CHANGELOG.md | 1 + packages/core-data/README.md | 14 +-- packages/core-data/src/actions.js | 124 +++++++--------------- packages/core-data/src/test/actions.js | 131 +++++++++++++++++++++--- 5 files changed, 160 insertions(+), 124 deletions(-) diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index 3aff1d1b221340..77e0566fdd882e 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -567,6 +567,7 @@ _Parameters_ - _query_ `?Object`: Special query parameters for the DELETE API call. - _options_ `[Object]`: Delete options. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. +- _options.throwOnError_ `[boolean]`: Whether to re-throw exceptions. If false, dispatching this action always resolves successfully and. never rejects. ### editEntityRecord @@ -734,18 +735,7 @@ _Parameters_ - _options_ `Object`: Saving options. - _options.isAutosave_ `[boolean]`: Whether this is an autosave. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. - -### throwingDeleteEntityRecord - -Undocumented declaration. - -### throwingSaveEditedEntityRecord - -Undocumented declaration. - -### throwingSaveEntityRecord - -Undocumented declaration. +- _options.throwOnError_ `[boolean]`: Whether to re-throw exceptions. If false, dispatching this action always resolves successfully and. never rejects. ### undo diff --git a/packages/core-data/CHANGELOG.md b/packages/core-data/CHANGELOG.md index 1ac48df5af1631..9a0512840a8838 100644 --- a/packages/core-data/CHANGELOG.md +++ b/packages/core-data/CHANGELOG.md @@ -1,6 +1,7 @@ ## Unreleased +– The saveEntityRecord, saveEditedEntityRecord, and deleteEntityRecord actions now accept an optional throwOnError option (defaults to false). When set to true, any exceptions occurring when the action was executing are re-thrown, causing dispatch().saveEntityRecord() to reject with an error. ([#39258](https://github.com/WordPress/gutenberg/pull/39258)) ## 4.2.0 (2022-03-11) diff --git a/packages/core-data/README.md b/packages/core-data/README.md index c9fed73ae0e6c0..af79963c2a3c49 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -70,6 +70,7 @@ _Parameters_ - _query_ `?Object`: Special query parameters for the DELETE API call. - _options_ `[Object]`: Delete options. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. +- _options.throwOnError_ `[boolean]`: Whether to re-throw exceptions. If false, dispatching this action always resolves successfully and. never rejects. ### editEntityRecord @@ -237,18 +238,7 @@ _Parameters_ - _options_ `Object`: Saving options. - _options.isAutosave_ `[boolean]`: Whether this is an autosave. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. - -### throwingDeleteEntityRecord - -Undocumented declaration. - -### throwingSaveEditedEntityRecord - -Undocumented declaration. - -### throwingSaveEntityRecord - -Undocumented declaration. +- _options.throwOnError_ `[boolean]`: Whether to re-throw exceptions. If false, dispatching this action always resolves successfully and. never rejects. ### undo diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index a657251cbda64b..4b61939e4f409d 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -209,22 +209,26 @@ export function receiveEmbedPreview( url, preview ) { /** * Action triggered to delete an entity record. * - * @param {string} kind Kind of the deleted entity record. - * @param {string} name Name of the deleted entity record. - * @param {string} recordId Record ID of the deleted entity record. - * @param {?Object} query Special query parameters for the - * DELETE API call. - * @param {Object} [options] Delete options. - * @param {Function} [options.__unstableFetch] Internal use only. Function to - * call instead of `apiFetch()`. - * Must return a promise. + * @param {string} kind Kind of the deleted entity. + * @param {string} name Name of the deleted entity. + * @param {string} recordId Record ID of the deleted entity. + * @param {?Object} query Special query parameters for the + * DELETE API call. + * @param {Object} [options] Delete options. + * @param {Function} [options.__unstableFetch] Internal use only. Function to + * call instead of `apiFetch()`. + * Must return a promise. + * @param {boolean} [options.throwOnError=false] Whether to re-throw exceptions. + * If false, dispatching this action + * always resolves successfully and. + * never rejects. */ export const deleteEntityRecord = ( kind, name, recordId, query, - { __unstableFetch = apiFetch } = {} + { __unstableFetch = apiFetch, throwOnError = false } = {} ) => async ( { dispatch } ) => { const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); const entityConfig = find( configs, { kind, name } ); @@ -273,6 +277,10 @@ export const deleteEntityRecord = ( error, } ); + if ( error && throwOnError ) { + throw error; + } + return deletedRecord; } finally { dispatch.__unstableReleaseStoreLock( lock ); @@ -386,20 +394,28 @@ export function __unstableCreateUndoLevel() { /** * Action triggered to save an entity record. * - * @param {string} kind Kind of the received entity. - * @param {string} name Name of the received entity. - * @param {Object} record Record to be saved. - * @param {Object} options Saving options. - * @param {boolean} [options.isAutosave=false] Whether this is an autosave. - * @param {Function} [options.__unstableFetch] Internal use only. Function to - * call instead of `apiFetch()`. - * Must return a promise. + * @param {string} kind Kind of the received entity. + * @param {string} name Name of the received entity. + * @param {Object} record Record to be saved. + * @param {Object} options Saving options. + * @param {boolean} [options.isAutosave=false] Whether this is an autosave. + * @param {Function} [options.__unstableFetch] Internal use only. Function to + * call instead of `apiFetch()`. + * Must return a promise. + * @param {boolean} [options.throwOnError=false] Whether to re-throw exceptions. + * If false, dispatching this action + * always resolves successfully and. + * never rejects. */ export const saveEntityRecord = ( kind, name, record, - { isAutosave = false, __unstableFetch = apiFetch } = {} + { + isAutosave = false, + __unstableFetch = apiFetch, + throwOnError = false, + } = {} ) => async ( { select, resolveSelect, dispatch } ) => { const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); const entityConfig = find( configs, { kind, name } ); @@ -578,6 +594,10 @@ export const saveEntityRecord = ( isAutosave, } ); + if ( error && throwOnError ) { + throw error; + } + return updatedRecord; } finally { dispatch.__unstableReleaseStoreLock( lock ); @@ -759,69 +779,3 @@ export function receiveAutosaves( postId, autosaves ) { autosaves: castArray( autosaves ), }; } - -export const throwingSaveEntityRecord = ( ...args ) => async ( { - dispatch, - select, -} ) => { - const [ kind, name, record ] = args; - const recordId = await dispatch( getRecordPk( kind, name, record ) ); - - const result = await dispatch( saveEntityRecord( ...args ) ); - if ( ! result ) { - if ( recordId ) { - const error = select.getLastEntitySaveError( kind, name, recordId ); - throw error; - } else { - throw new Error( 'Something went wrong' ); - } - } - - const resultId = await dispatch( getRecordPk( kind, name, result ) ); - const error = select.getLastEntitySaveError( kind, name, resultId ); - - if ( error ) { - throw error; - } - return result; -}; - -export const throwingSaveEditedEntityRecord = ( ...args ) => async ( { - dispatch, - select, -} ) => { - const result = await dispatch( saveEditedEntityRecord( ...args ) ); - - const [ kind, name, id ] = args; - const error = select.getLastEntitySaveError( kind, name, id ); - - if ( error || ! result ) { - throw error; - } - return result; -}; - -export const throwingDeleteEntityRecord = ( ...args ) => async ( { - dispatch, - select, -} ) => { - const result = await dispatch( deleteEntityRecord( ...args ) ); - - const [ kind, name, id ] = args; - const error = select.getLastEntityDeleteError( kind, name, id ); - - if ( error || ! result ) { - throw error; - } - return result; -}; - -const getRecordPk = ( kind, name, record ) => async ( { dispatch } ) => { - const entities = await dispatch( getKindEntities( kind ) ); - const entity = find( entities, { kind, name } ); - if ( ! entity || entity?.__experimentalNoFetch ) { - return; - } - const entityIdKey = entity.key || DEFAULT_ENTITY_KEY; - return record[ entityIdKey ]; -}; diff --git a/packages/core-data/src/test/actions.js b/packages/core-data/src/test/actions.js index e902c532a6193c..8a73a3b3040b59 100644 --- a/packages/core-data/src/test/actions.js +++ b/packages/core-data/src/test/actions.js @@ -110,6 +110,68 @@ describe( 'deleteEntityRecord', () => { expect( result ).toBe( deletedRecord ); } ); + + it( 'throws on error when throwOnError is true', async () => { + const entities = [ + { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' }, + ]; + + const dispatch = Object.assign( jest.fn(), { + receiveEntityRecords: jest.fn(), + __unstableAcquireStoreLock: jest.fn(), + __unstableReleaseStoreLock: jest.fn(), + } ); + // Provide entities + dispatch.mockReturnValueOnce( entities ); + + // Provide response + apiFetch.mockImplementation( () => { + throw new Error( 'API error' ); + } ); + + await expect( + deleteEntityRecord( + 'postType', + 'post', + 10, + {}, + { + throwOnError: true, + } + )( { dispatch } ) + ).rejects.toEqual( new Error( 'API error' ) ); + } ); + + it( 'resolves on error when throwOnError is false', async () => { + const entities = [ + { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' }, + ]; + + const dispatch = Object.assign( jest.fn(), { + receiveEntityRecords: jest.fn(), + __unstableAcquireStoreLock: jest.fn(), + __unstableReleaseStoreLock: jest.fn(), + } ); + // Provide entities + dispatch.mockReturnValueOnce( entities ); + + // Provide response + apiFetch.mockImplementation( () => { + throw new Error( 'API error' ); + } ); + + await expect( + deleteEntityRecord( + 'postType', + 'post', + 10, + {}, + { + throwOnError: false, + } + )( { dispatch } ) + ).resolves.toBe( false ); + } ); } ); describe( 'saveEditedEntityRecord', () => { @@ -201,9 +263,15 @@ describe( 'saveEditedEntityRecord', () => { } ); describe( 'saveEntityRecord', () => { + let dispatch; beforeEach( async () => { apiFetch.mockReset(); jest.useFakeTimers(); + dispatch = Object.assign( jest.fn(), { + receiveEntityRecords: jest.fn(), + __unstableAcquireStoreLock: jest.fn(), + __unstableReleaseStoreLock: jest.fn(), + } ); } ); it( 'triggers a POST request for a new record', async () => { @@ -215,11 +283,6 @@ describe( 'saveEntityRecord', () => { getRawEntityRecord: () => post, }; - const dispatch = Object.assign( jest.fn(), { - receiveEntityRecords: jest.fn(), - __unstableAcquireStoreLock: jest.fn(), - __unstableReleaseStoreLock: jest.fn(), - } ); // Provide entities dispatch.mockReturnValueOnce( configs ); @@ -278,6 +341,54 @@ describe( 'saveEntityRecord', () => { expect( result ).toBe( updatedRecord ); } ); + it( 'throws on error when throwOnError is true', async () => { + const post = { title: 'new post' }; + const entities = [ + { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' }, + ]; + const select = { + getRawEntityRecord: () => post, + }; + + // Provide entities + dispatch.mockReturnValueOnce( entities ); + + // Provide response + apiFetch.mockImplementation( () => { + throw new Error( 'API error' ); + } ); + + await expect( + saveEntityRecord( 'postType', 'post', post, { + throwOnError: true, + } )( { select, dispatch } ) + ).rejects.toEqual( new Error( 'API error' ) ); + } ); + + it( 'resolves on error when throwOnError is false', async () => { + const post = { title: 'new post' }; + const entities = [ + { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' }, + ]; + const select = { + getRawEntityRecord: () => post, + }; + + // Provide entities + dispatch.mockReturnValueOnce( entities ); + + // Provide response + apiFetch.mockImplementation( () => { + throw new Error( 'API error' ); + } ); + + await expect( + saveEntityRecord( 'postType', 'post', post, { + throwOnError: false, + } )( { select, dispatch } ) + ).resolves.toEqual( undefined ); + } ); + it( 'triggers a PUT request for an existing record', async () => { const post = { id: 10, title: 'new post' }; const configs = [ @@ -287,11 +398,6 @@ describe( 'saveEntityRecord', () => { getRawEntityRecord: () => post, }; - const dispatch = Object.assign( jest.fn(), { - receiveEntityRecords: jest.fn(), - __unstableAcquireStoreLock: jest.fn(), - __unstableReleaseStoreLock: jest.fn(), - } ); // Provide entities dispatch.mockReturnValueOnce( configs ); @@ -364,11 +470,6 @@ describe( 'saveEntityRecord', () => { getRawEntityRecord: () => ( {} ), }; - const dispatch = Object.assign( jest.fn(), { - receiveEntityRecords: jest.fn(), - __unstableAcquireStoreLock: jest.fn(), - __unstableReleaseStoreLock: jest.fn(), - } ); // Provide entities dispatch.mockReturnValueOnce( configs ); From a3619495afdf67100bd90cef18e7a7ecae9d8da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 11 Mar 2022 11:09:51 +0100 Subject: [PATCH 07/14] Use a more succint docstring --- docs/reference-guides/data/data-core.md | 4 ++-- packages/core-data/README.md | 4 ++-- packages/core-data/src/actions.js | 12 ++++-------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index 77e0566fdd882e..bc82a183183cae 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -567,7 +567,7 @@ _Parameters_ - _query_ `?Object`: Special query parameters for the DELETE API call. - _options_ `[Object]`: Delete options. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. -- _options.throwOnError_ `[boolean]`: Whether to re-throw exceptions. If false, dispatching this action always resolves successfully and. never rejects. +- _options.throwOnError_ `[boolean]`: If false, this action suppresses all the exceptions. ### editEntityRecord @@ -735,7 +735,7 @@ _Parameters_ - _options_ `Object`: Saving options. - _options.isAutosave_ `[boolean]`: Whether this is an autosave. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. -- _options.throwOnError_ `[boolean]`: Whether to re-throw exceptions. If false, dispatching this action always resolves successfully and. never rejects. +- _options.throwOnError_ `[boolean]`: If false, this action suppresses all the exceptions. ### undo diff --git a/packages/core-data/README.md b/packages/core-data/README.md index af79963c2a3c49..d8c2957b7c5056 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -70,7 +70,7 @@ _Parameters_ - _query_ `?Object`: Special query parameters for the DELETE API call. - _options_ `[Object]`: Delete options. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. -- _options.throwOnError_ `[boolean]`: Whether to re-throw exceptions. If false, dispatching this action always resolves successfully and. never rejects. +- _options.throwOnError_ `[boolean]`: If false, this action suppresses all the exceptions. ### editEntityRecord @@ -238,7 +238,7 @@ _Parameters_ - _options_ `Object`: Saving options. - _options.isAutosave_ `[boolean]`: Whether this is an autosave. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. -- _options.throwOnError_ `[boolean]`: Whether to re-throw exceptions. If false, dispatching this action always resolves successfully and. never rejects. +- _options.throwOnError_ `[boolean]`: If false, this action suppresses all the exceptions. ### undo diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 4b61939e4f409d..625055873b86b1 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -218,10 +218,8 @@ export function receiveEmbedPreview( url, preview ) { * @param {Function} [options.__unstableFetch] Internal use only. Function to * call instead of `apiFetch()`. * Must return a promise. - * @param {boolean} [options.throwOnError=false] Whether to re-throw exceptions. - * If false, dispatching this action - * always resolves successfully and. - * never rejects. + * @param {boolean} [options.throwOnError=false] If false, this action suppresses all + * the exceptions. */ export const deleteEntityRecord = ( kind, @@ -402,10 +400,8 @@ export function __unstableCreateUndoLevel() { * @param {Function} [options.__unstableFetch] Internal use only. Function to * call instead of `apiFetch()`. * Must return a promise. - * @param {boolean} [options.throwOnError=false] Whether to re-throw exceptions. - * If false, dispatching this action - * always resolves successfully and. - * never rejects. + * @param {boolean} [options.throwOnError=false] If false, this action suppresses all + * the exceptions. */ export const saveEntityRecord = ( kind, From 2d63a509186d1b256cbe695ad547da4131593d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 11 Mar 2022 11:20:33 +0100 Subject: [PATCH 08/14] Update usage --- packages/edit-navigation/src/store/actions.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/edit-navigation/src/store/actions.js b/packages/edit-navigation/src/store/actions.js index 13946bd9edd98b..d811ce2564c6e9 100644 --- a/packages/edit-navigation/src/store/actions.js +++ b/packages/edit-navigation/src/store/actions.js @@ -56,7 +56,9 @@ export const saveNavigationPost = ( post ) => async ( { // Save menu. await registry .dispatch( coreDataStore ) - .throwingSaveEditedEntityRecord( 'root', 'menu', menuId ); + .saveEditedEntityRecord( 'root', 'menu', menuId, { + throwOnError: true, + } ); // Save menu items. const updatedBlocks = await dispatch( From 5b7b6e8e2339d892f9ceda92d479debee954c44d Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Fri, 11 Mar 2022 11:21:25 +0100 Subject: [PATCH 09/14] Update packages/core-data/CHANGELOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Greg Ziółkowski --- packages/core-data/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core-data/CHANGELOG.md b/packages/core-data/CHANGELOG.md index 9a0512840a8838..c8a308c5d03087 100644 --- a/packages/core-data/CHANGELOG.md +++ b/packages/core-data/CHANGELOG.md @@ -1,6 +1,8 @@ ## Unreleased + +### New Features – The saveEntityRecord, saveEditedEntityRecord, and deleteEntityRecord actions now accept an optional throwOnError option (defaults to false). When set to true, any exceptions occurring when the action was executing are re-thrown, causing dispatch().saveEntityRecord() to reject with an error. ([#39258](https://github.com/WordPress/gutenberg/pull/39258)) ## 4.2.0 (2022-03-11) From 26530dfdaada9c320474aab6aad987c6f0e38ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 11 Mar 2022 14:16:24 +0100 Subject: [PATCH 10/14] Clarify the default throwOnError behavior --- docs/reference-guides/data/data-core.md | 4 ++-- packages/core-data/README.md | 4 ++-- packages/core-data/src/actions.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index bc82a183183cae..7cc2002384c6a7 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -567,7 +567,7 @@ _Parameters_ - _query_ `?Object`: Special query parameters for the DELETE API call. - _options_ `[Object]`: Delete options. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. -- _options.throwOnError_ `[boolean]`: If false, this action suppresses all the exceptions. +- _options.throwOnError_ `[boolean]`: If false, this action suppresses all the exceptions. Defaults to false. ### editEntityRecord @@ -735,7 +735,7 @@ _Parameters_ - _options_ `Object`: Saving options. - _options.isAutosave_ `[boolean]`: Whether this is an autosave. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. -- _options.throwOnError_ `[boolean]`: If false, this action suppresses all the exceptions. +- _options.throwOnError_ `[boolean]`: If false, this action suppresses all the exceptions. Defaults to false. ### undo diff --git a/packages/core-data/README.md b/packages/core-data/README.md index d8c2957b7c5056..82086efa5ab327 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -70,7 +70,7 @@ _Parameters_ - _query_ `?Object`: Special query parameters for the DELETE API call. - _options_ `[Object]`: Delete options. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. -- _options.throwOnError_ `[boolean]`: If false, this action suppresses all the exceptions. +- _options.throwOnError_ `[boolean]`: If false, this action suppresses all the exceptions. Defaults to false. ### editEntityRecord @@ -238,7 +238,7 @@ _Parameters_ - _options_ `Object`: Saving options. - _options.isAutosave_ `[boolean]`: Whether this is an autosave. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. -- _options.throwOnError_ `[boolean]`: If false, this action suppresses all the exceptions. +- _options.throwOnError_ `[boolean]`: If false, this action suppresses all the exceptions. Defaults to false. ### undo diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 625055873b86b1..c9eb90434fb4c0 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -219,7 +219,7 @@ export function receiveEmbedPreview( url, preview ) { * call instead of `apiFetch()`. * Must return a promise. * @param {boolean} [options.throwOnError=false] If false, this action suppresses all - * the exceptions. + * the exceptions. Defaults to false. */ export const deleteEntityRecord = ( kind, @@ -401,7 +401,7 @@ export function __unstableCreateUndoLevel() { * call instead of `apiFetch()`. * Must return a promise. * @param {boolean} [options.throwOnError=false] If false, this action suppresses all - * the exceptions. + * the exceptions. Defaults to false. */ export const saveEntityRecord = ( kind, From 717fa02845c58c3c8b3b14762e2608b4f5dd2b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 15 Mar 2022 12:47:43 +0100 Subject: [PATCH 11/14] Add hasError flag to throw even falsy errors --- docs/reference-guides/data/data-core.md | 6 +++--- packages/core-data/README.md | 6 +++--- packages/core-data/src/actions.js | 8 ++++++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index 7cc2002384c6a7..da5dd9af47e122 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -561,9 +561,9 @@ Action triggered to delete an entity record. _Parameters_ -- _kind_ `string`: Kind of the deleted entity record. -- _name_ `string`: Name of the deleted entity record. -- _recordId_ `string`: Record ID of the deleted entity record. +- _kind_ `string`: Kind of the deleted entity. +- _name_ `string`: Name of the deleted entity. +- _recordId_ `string`: Record ID of the deleted entity. - _query_ `?Object`: Special query parameters for the DELETE API call. - _options_ `[Object]`: Delete options. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. diff --git a/packages/core-data/README.md b/packages/core-data/README.md index 82086efa5ab327..d19c0a11ad0b01 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -64,9 +64,9 @@ Action triggered to delete an entity record. _Parameters_ -- _kind_ `string`: Kind of the deleted entity record. -- _name_ `string`: Name of the deleted entity record. -- _recordId_ `string`: Record ID of the deleted entity record. +- _kind_ `string`: Kind of the deleted entity. +- _name_ `string`: Name of the deleted entity. +- _recordId_ `string`: Record ID of the deleted entity. - _query_ `?Object`: Special query parameters for the DELETE API call. - _options_ `[Object]`: Delete options. - _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise. diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index c9eb90434fb4c0..a87b82c4cb5152 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -250,6 +250,7 @@ export const deleteEntityRecord = ( recordId, } ); + let hasError = false; try { let path = `${ entityConfig.baseURL }/${ recordId }`; @@ -264,6 +265,7 @@ export const deleteEntityRecord = ( await dispatch( removeItems( kind, name, recordId, true ) ); } catch ( _error ) { + hasError = true; error = _error; } @@ -275,7 +277,7 @@ export const deleteEntityRecord = ( error, } ); - if ( error && throwOnError ) { + if ( hasError && throwOnError ) { throw error; } @@ -457,6 +459,7 @@ export const saveEntityRecord = ( } ); let updatedRecord; let error; + let hasError = false; try { const path = `${ entityConfig.baseURL }${ recordId ? '/' + recordId : '' @@ -579,6 +582,7 @@ export const saveEntityRecord = ( ); } } catch ( _error ) { + hasError = true; error = _error; } dispatch( { @@ -590,7 +594,7 @@ export const saveEntityRecord = ( isAutosave, } ); - if ( error && throwOnError ) { + if ( hasError && throwOnError ) { throw error; } From 889acd97c442da2ddcf514a2345c63f3e0f1bbae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 16 Mar 2022 11:12:26 +0100 Subject: [PATCH 12/14] Remove the usage of throwing* actions (a regression?) --- .../components/header/template-title/delete-template.js | 4 ++-- .../src/components/add-new-template/new-template-part.js | 7 ++++--- .../src/components/list/actions/rename-menu-item.js | 7 ++++--- packages/edit-widgets/src/store/actions.js | 5 +++-- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/edit-post/src/components/header/template-title/delete-template.js b/packages/edit-post/src/components/header/template-title/delete-template.js index 26283fc71e1bc7..ec10c5ad899121 100644 --- a/packages/edit-post/src/components/header/template-title/delete-template.js +++ b/packages/edit-post/src/components/header/template-title/delete-template.js @@ -28,7 +28,7 @@ export default function DeleteTemplate() { const { setIsEditingTemplate } = useDispatch( editPostStore ); const { getEditorSettings } = useSelect( editorStore ); const { updateEditorSettings, editPost } = useDispatch( editorStore ); - const { throwingDeleteEntityRecord } = useDispatch( coreStore ); + const { deleteEntityRecord } = useDispatch( coreStore ); const { template } = useSelect( ( select ) => { const { isEditingTemplate, getEditedPostTemplate } = select( editPostStore @@ -67,7 +67,7 @@ export default function DeleteTemplate() { ...settings, availableTemplates: newAvailableTemplates, } ); - throwingDeleteEntityRecord( 'postType', 'wp_template', template.id ); + deleteEntityRecord( 'postType', 'wp_template', template.id, { throwOnError: true } ); }; return ( diff --git a/packages/edit-site/src/components/add-new-template/new-template-part.js b/packages/edit-site/src/components/add-new-template/new-template-part.js index 032b5716bc12cf..d2e396f6580dbb 100644 --- a/packages/edit-site/src/components/add-new-template/new-template-part.js +++ b/packages/edit-site/src/components/add-new-template/new-template-part.js @@ -23,7 +23,7 @@ export default function NewTemplatePart( { postType } ) { const history = useHistory(); const [ isModalOpen, setIsModalOpen ] = useState( false ); const { createErrorNotice } = useDispatch( noticesStore ); - const { throwingSaveEntityRecord } = useDispatch( coreStore ); + const { saveEntityRecord } = useDispatch( coreStore ); async function createTemplatePart( { title, area } ) { if ( ! title ) { @@ -40,7 +40,7 @@ export default function NewTemplatePart( { postType } ) { kebabCase( title ).replace( /[^\w-]+/g, '' ) || 'wp-custom-part'; - const templatePart = await throwingSaveEntityRecord( + const templatePart = await saveEntityRecord( 'postType', 'wp_template_part', { @@ -48,7 +48,8 @@ export default function NewTemplatePart( { postType } ) { title, content: '', area, - } + }, + { throwOnError: true } ); setIsModalOpen( false ); diff --git a/packages/edit-site/src/components/list/actions/rename-menu-item.js b/packages/edit-site/src/components/list/actions/rename-menu-item.js index 219515bceaf815..1ce4cc843035be 100644 --- a/packages/edit-site/src/components/list/actions/rename-menu-item.js +++ b/packages/edit-site/src/components/list/actions/rename-menu-item.js @@ -19,7 +19,7 @@ export default function RenameMenuItem( { template, onClose } ) { const [ title, setTitle ] = useState( () => template.title.rendered ); const [ isModalOpen, setIsModalOpen ] = useState( false ); - const { editEntityRecord, throwingSaveEditedEntityRecord } = useDispatch( + const { editEntityRecord, saveEditedEntityRecord } = useDispatch( coreStore ); const { createSuccessNotice, createErrorNotice } = useDispatch( @@ -44,10 +44,11 @@ export default function RenameMenuItem( { template, onClose } ) { onClose(); // Persist edited entity. - await throwingSaveEditedEntityRecord( + await saveEditedEntityRecord( 'postType', template.type, - template.id + template.id, + { throwOnError: true } ); createSuccessNotice( __( 'Entity renamed.' ), { diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js index 76dc87f090df90..96c717dd881070 100644 --- a/packages/edit-widgets/src/store/actions.js +++ b/packages/edit-widgets/src/store/actions.js @@ -284,10 +284,11 @@ export const saveWidgetArea = ( widgetAreaId ) => async ( { const trySaveWidgetArea = ( widgetAreaId ) => ( { registry } ) => { registry .dispatch( coreStore ) - .throwingSaveEditedEntityRecord( + .saveEditedEntityRecord( KIND, WIDGET_AREA_ENTITY_TYPE, - widgetAreaId + widgetAreaId, + { throwOnError: true } ); }; From 245ed3af877da03a624cfe14c9ea032a6bf624b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 16 Mar 2022 11:13:13 +0100 Subject: [PATCH 13/14] Lint --- .../components/header/template-title/delete-template.js | 4 +++- packages/edit-widgets/src/store/actions.js | 9 +++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/edit-post/src/components/header/template-title/delete-template.js b/packages/edit-post/src/components/header/template-title/delete-template.js index ec10c5ad899121..c550b552bbd7e7 100644 --- a/packages/edit-post/src/components/header/template-title/delete-template.js +++ b/packages/edit-post/src/components/header/template-title/delete-template.js @@ -67,7 +67,9 @@ export default function DeleteTemplate() { ...settings, availableTemplates: newAvailableTemplates, } ); - deleteEntityRecord( 'postType', 'wp_template', template.id, { throwOnError: true } ); + deleteEntityRecord( 'postType', 'wp_template', template.id, { + throwOnError: true, + } ); }; return ( diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js index 96c717dd881070..392b552c6ad57f 100644 --- a/packages/edit-widgets/src/store/actions.js +++ b/packages/edit-widgets/src/store/actions.js @@ -284,12 +284,9 @@ export const saveWidgetArea = ( widgetAreaId ) => async ( { const trySaveWidgetArea = ( widgetAreaId ) => ( { registry } ) => { registry .dispatch( coreStore ) - .saveEditedEntityRecord( - KIND, - WIDGET_AREA_ENTITY_TYPE, - widgetAreaId, - { throwOnError: true } - ); + .saveEditedEntityRecord( KIND, WIDGET_AREA_ENTITY_TYPE, widgetAreaId, { + throwOnError: true, + } ); }; /** From 9f847afa5ed36d04ded2163be2c742a6ab525329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 16 Mar 2022 11:14:48 +0100 Subject: [PATCH 14/14] Remove one last usage of throwingSaveEntityRecord --- .../src/components/add-new-template/new-template.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/edit-site/src/components/add-new-template/new-template.js b/packages/edit-site/src/components/add-new-template/new-template.js index 610d01207ff524..3340d37fb21397 100644 --- a/packages/edit-site/src/components/add-new-template/new-template.js +++ b/packages/edit-site/src/components/add-new-template/new-template.js @@ -84,7 +84,7 @@ export default function NewTemplate( { postType } ) { } ), [] ); - const { throwingSaveEntityRecord } = useDispatch( coreStore ); + const { saveEntityRecord } = useDispatch( coreStore ); const { createErrorNotice } = useDispatch( noticesStore ); async function createTemplate( { slug } ) { @@ -93,7 +93,7 @@ export default function NewTemplate( { postType } ) { slug, } ); - const template = await throwingSaveEntityRecord( + const template = await saveEntityRecord( 'postType', 'wp_template', { @@ -102,7 +102,8 @@ export default function NewTemplate( { postType } ) { slug: slug.toString(), status: 'publish', title, - } + }, + { throwOnError: true } ); // Navigate to the created template editor.