Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add the missing imports to the docstring example
  • Loading branch information
adamziel committed Aug 5, 2022
commit 897c3e666eb8fcc71aa5e769bcefeebb73e588be
20 changes: 10 additions & 10 deletions packages/core-data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -765,11 +765,18 @@ 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 );
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.

const { createSuccessNotice, createErrorNotice } =
useDispatch( noticeStore );

if ( page.isResolving ) {
return 'Loading...';
Expand All @@ -783,13 +790,8 @@ function PageRenameForm( { id } ) {
createSuccessNotice( __( 'Page renamed.' ), {
type: 'snackbar',
} );
} catch ( e ) {
const errorMessage =
error.message && error.code !== 'unknown_error'
? error.message
: __( 'An error occurred while renaming the entity.' );

createErrorNotice( errorMessage, { type: 'snackbar' } );
} catch ( error ) {
createErrorNotice( error.message, { type: 'snackbar' } );
}
}

Expand All @@ -800,9 +802,7 @@ function PageRenameForm( { id } ) {
value={ title }
onChange={ setTitle }
/>
<Button variant="primary" type="submit">
{ __( 'Save' ) }
</Button>
<button type="submit">{ __( 'Save' ) }</button>
</form>
);
}
Expand Down
68 changes: 34 additions & 34 deletions packages/core-data/src/hooks/use-entity-record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,46 +84,46 @@ export interface Options {
*
* @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 page = useEntityRecord( 'postType', 'page', id );
* const [ title, setTitle ] = useState( () => page.record.title.rendered );
* const { createSuccessNotice, createErrorNotice } =
* useDispatch( noticeStore );
*
* if ( page.isResolving ) {
* return 'Loading...';
* }
* if ( page.isResolving ) {
* return 'Loading...';
* }
*
* async function onRename( event ) {
* event.preventDefault();
* page.edit({ title });
* try {
* await page.save()
* createSuccessNotice( __( 'Page renamed.' ), {
* type: 'snackbar',
* } );
* } catch(e) {
* const errorMessage =
* error.message && error.code !== 'unknown_error'
* ? error.message
* : __( 'An error occurred while renaming the entity.' );
*
* createErrorNotice( errorMessage, { type: 'snackbar' } );
* }
* }
* 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 (
* <form onSubmit={ onRename }>
* <TextControl
* label={ __( 'Name' ) }
* value={ title }
* onChange={ setTitle }
* />
* <Button variant="primary" type="submit">
* { __( 'Save' ) }
* </Button>
* </form>
* );
* return (
* <form onSubmit={ onRename }>
* <TextControl
* label={ __( 'Name' ) }
* value={ title }
* onChange={ setTitle }
* />
* <button type="submit">{ __( 'Save' ) }</button>
* </form>
* );
* }
*
* // Rendered in the application:
Expand Down