diff --git a/packages/core-data/README.md b/packages/core-data/README.md index 5740e5d6d5740..2a5631013e77a 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -764,6 +764,57 @@ In the above example, when `PageTitleDisplay` is rendered into an application, the page and the resolution details will be retrieved from the store state using `getEntityRecord()`, or resolved if missing. +```js +import { useState } from '@wordpress/data'; +import { useDispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { TextControl } from '@wordpress/components'; +import { store as noticeStore } from '@wordpress/notices'; +import { useEntityRecord } from '@wordpress/core-data'; + +function PageRenameForm( { id } ) { + const page = useEntityRecord( 'postType', 'page', id ); + const [ title, setTitle ] = useState( () => page.record.title.rendered ); + const { createSuccessNotice, createErrorNotice } = + useDispatch( noticeStore ); + + if ( page.isResolving ) { + return 'Loading...'; + } + + async function onRename( event ) { + event.preventDefault(); + page.edit( { title } ); + try { + await page.save(); + createSuccessNotice( __( 'Page renamed.' ), { + type: 'snackbar', + } ); + } catch ( error ) { + createErrorNotice( error.message, { type: 'snackbar' } ); + } + } + + return ( +
+ + + + ); +} + +// Rendered in the application: +// +``` + +In the above example, updating and saving the page title is handled +via the `edit()` and `save()` mutation helpers provided by +`useEntityRecord()`; + _Parameters_ - _kind_ `string`: Kind of the entity, e.g. `root` or a `postType`. See rootEntitiesConfig in ../entities.ts for a list of available kinds. diff --git a/packages/core-data/src/hooks/test/use-entity-record.js b/packages/core-data/src/hooks/test/use-entity-record.js index fa67aded0c578..524c7d8348c08 100644 --- a/packages/core-data/src/hooks/test/use-entity-record.js +++ b/packages/core-data/src/hooks/test/use-entity-record.js @@ -50,7 +50,11 @@ describe( 'useEntityRecord', () => { ); expect( data ).toEqual( { - records: undefined, + edit: expect.any( Function ), + editedRecord: {}, + hasEdits: false, + record: undefined, + save: expect.any( Function ), hasResolved: false, isResolving: false, status: 'IDLE', @@ -66,7 +70,11 @@ describe( 'useEntityRecord', () => { } ); expect( data ).toEqual( { + edit: expect.any( Function ), + editedRecord: {}, + hasEdits: false, record: { hello: 'world', id: 1 }, + save: expect.any( Function ), hasResolved: true, isResolving: false, status: 'SUCCESS', diff --git a/packages/core-data/src/hooks/use-entity-record.ts b/packages/core-data/src/hooks/use-entity-record.ts index e751663f16804..a1f1a6c89fa07 100644 --- a/packages/core-data/src/hooks/use-entity-record.ts +++ b/packages/core-data/src/hooks/use-entity-record.ts @@ -1,7 +1,9 @@ /** * WordPress dependencies */ +import { useDispatch, useSelect } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; +import { useMemo } from '@wordpress/element'; /** * Internal dependencies @@ -14,11 +16,25 @@ export interface EntityRecordResolution< RecordType > { /** The requested entity record */ record: RecordType | null; + /** The edited entity record */ + editedRecord: Partial< RecordType >; + + /** Apply local (in-browser) edits to the edited entity record */ + edit: ( diff: Partial< RecordType > ) => void; + + /** Persist the edits to the server */ + save: () => Promise< void >; + /** * Is the record still being resolved? */ isResolving: boolean; + /** + * Does the record have any local edits? + */ + hasEdits: boolean; + /** * Is the record resolved by now? */ @@ -66,6 +82,58 @@ export interface Options { * application, the page and the resolution details will be retrieved from * the store state using `getEntityRecord()`, or resolved if missing. * + * @example + * ```js + * import { useState } from '@wordpress/data'; + * import { useDispatch } from '@wordpress/data'; + * import { __ } from '@wordpress/i18n'; + * import { TextControl } from '@wordpress/components'; + * import { store as noticeStore } from '@wordpress/notices'; + * import { useEntityRecord } from '@wordpress/core-data'; + * + * function PageRenameForm( { id } ) { + * const page = useEntityRecord( 'postType', 'page', id ); + * const [ title, setTitle ] = useState( () => page.record.title.rendered ); + * const { createSuccessNotice, createErrorNotice } = + * useDispatch( noticeStore ); + * + * if ( page.isResolving ) { + * return 'Loading...'; + * } + * + * async function onRename( event ) { + * event.preventDefault(); + * page.edit( { title } ); + * try { + * await page.save(); + * createSuccessNotice( __( 'Page renamed.' ), { + * type: 'snackbar', + * } ); + * } catch ( error ) { + * createErrorNotice( error.message, { type: 'snackbar' } ); + * } + * } + * + * return ( + *
+ * + * + * + * ); + * } + * + * // Rendered in the application: + * // + * ``` + * + * In the above example, updating and saving the page title is handled + * via the `edit()` and `save()` mutation helpers provided by + * `useEntityRecord()`; + * * @return Entity record data. * @template RecordType */ @@ -75,7 +143,31 @@ export default function useEntityRecord< RecordType >( recordId: string | number, options: Options = { enabled: true } ): EntityRecordResolution< RecordType > { - const { data: record, ...rest } = useQuerySelect( + const { editEntityRecord, saveEditedEntityRecord } = + useDispatch( coreStore ); + + const mutations = useMemo( + () => ( { + edit: ( record ) => + editEntityRecord( kind, name, recordId, record ), + save: ( saveOptions: any = {} ) => + saveEditedEntityRecord( kind, name, recordId, { + throwOnError: true, + ...saveOptions, + } ), + } ), + [ recordId ] + ); + + const { editedRecord, hasEdits } = useSelect( + ( select ) => ( { + editedRecord: select( coreStore ).getEditedEntityRecord(), + hasEdits: select( coreStore ).hasEditsForEntityRecord(), + } ), + [ kind, name, recordId ] + ); + + const { data: record, ...querySelectRest } = useQuerySelect( ( query ) => { if ( ! options.enabled ) { return null; @@ -87,7 +179,10 @@ export default function useEntityRecord< RecordType >( return { record, - ...rest, + editedRecord, + hasEdits, + ...querySelectRest, + ...mutations, }; }