Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
5 changes: 3 additions & 2 deletions packages/core-data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ edit to an entity record, if any.

_Returns_

- `undefined`:
- `undefined`:

### saveEditedEntityRecord

Expand Down Expand Up @@ -249,7 +249,8 @@ an entity record, if any.

_Returns_

- `undefined`:
- `undefined`:


<!-- END TOKEN(Autogenerated actions|src/actions.js) -->

Expand Down
5 changes: 5 additions & 0 deletions packages/core-data/src/hooks/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const INACTIVE = 'INACTIVE';
export const IDLE = 'IDLE';
export const RESOLVING = 'RESOLVING';
export const ERROR = 'ERROR';
export const SUCCESS = 'SUCCESS';
53 changes: 53 additions & 0 deletions packages/core-data/src/hooks/use-entity-record-create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* WordPress dependencies
*/
import { useMemo } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';

/**
* Internal dependencies
*/
import { IDLE, ERROR, RESOLVING } from './constants';
import { store as coreStore } from '../';

export default function useEntityRecordCreate( kind, type ) {
const { saveEntityRecord } = useDispatch( coreStore );

const mutations = useMemo(
() => ( {
create: ( record ) => saveEntityRecord( kind, type, record ),
} ),
[]
);

const state = useSelect(
( select ) => {
const args = [ kind, type ];
const { getLastEntitySaveError, isSavingEntityRecord } = select(
coreStore
);
const error = getLastEntitySaveError( ...args );
const isSaving = isSavingEntityRecord( ...args );

let status;
if ( isSaving ) {
status = RESOLVING;
} else if ( error ) {
status = ERROR;
} else {
status = IDLE;
}
return {
status,
error,
isSaving,
};
},
[ kind, type ]
);

return {
...mutations,
...state,
};
}
69 changes: 69 additions & 0 deletions packages/core-data/src/hooks/use-entity-record-update.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* WordPress dependencies
*/
import { useMemo } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';

/**
* Internal dependencies
*/
import { store as coreStore } from '../';
import { IDLE, ERROR, RESOLVING } from './constants';

export default function useEntityRecordUpdate( kind, type, id ) {
const {
editEntityRecord,
saveEditedEntityRecord,
deleteEntityRecord,
} = useDispatch( coreStore );

const mutations = useMemo(
() => ( {
edit: ( record ) => editEntityRecord( kind, type, id, record ),
save: () => saveEditedEntityRecord( kind, type, id ),
delete: () => deleteEntityRecord( kind, type, id ),
} ),
[ id ]
);

const state = useSelect(
( select ) => {
const args = [ kind, type, id ];
const {
getLastEntitySaveError,
getLastEntityDeleteError,
isSavingEntityRecord,
isDeletingEntityRecord,
} = select( coreStore );

const saveError = getLastEntitySaveError( ...args );
const deleteError = getLastEntityDeleteError( ...args );
const error = saveError || deleteError;

const isSaving = isSavingEntityRecord( ...args );
const isDeleting = isDeletingEntityRecord( ...args );

let status;
if ( isSaving || isDeleting ) {
status = RESOLVING;
} else if ( error ) {
status = ERROR;
} else {
status = IDLE;
}

return {
status,
error,
isSaving,
isDeleting,
};
},
[ kind, type, id ]
);

return {
...mutations,
...state,
};
}
75 changes: 75 additions & 0 deletions packages/core-data/src/hooks/use-entity-record.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* WordPress dependencies
*/
import { useQuerySelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { store as coreStore } from '../';
import { INACTIVE, IDLE, SUCCESS, ERROR, RESOLVING } from './constants';

export default function useEntityRecord(
kind,
type,
id,
options = { runIf: true }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It looks like a special case that could be handled outside the hook. Can you explain the rationale behind runIf and how do you envision its application?

) {
const {
data,
isMissing,
isResolving,
hasResolved,
editedRecord,
hasEdits,
} = useQuerySelect(
( resolve ) => {
if ( ! options.runIf ) {
return {
status: INACTIVE,
isMissing: true,
hasResolved: false,
hasEdits: false,
};
}

const {
getEntityRecord,
getEditedEntityRecord,
hasEditsForEntityRecord,
} = resolve( coreStore );
const args = [ kind, type, id ];
const record = getEntityRecord( ...args );
return {
...record,
isMissing: record.hasResolved && ! record.data,
editedRecord: getEditedEntityRecord( ...args ).data,
hasEdits: hasEditsForEntityRecord( ...args ).data,
};
},
[ options.runIf ]
);

let status;
if ( isResolving ) {
status = RESOLVING;
} else if ( hasResolved ) {
if ( data ) {
status = SUCCESS;
} else {
status = ERROR;
}
} else {
status = IDLE;
}

return {
status,
record: data,
editedRecord,
hasEdits,
isMissing,
isResolving,
hasResolved,
};
}
56 changes: 56 additions & 0 deletions packages/core-data/src/hooks/use-entity-records.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* WordPress dependencies
*/
import { useQuerySelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { INACTIVE, IDLE, SUCCESS, ERROR, RESOLVING } from './constants';
import { store as coreStore } from '../';

export default function useEntityRecords(
kind,
type,
query,
options = { runIf: true }
) {
return useQuerySelect(
( resolve ) => {
if ( ! options.runIf ) {
return {
records: null,
status: INACTIVE,
hasResolved: false,
hasRecords: false,
};
}
const { data, ...state } = resolve( coreStore ).getEntityRecords(
kind,
type,
query
);

let status;
if ( state.isResolving ) {
status = RESOLVING;
} else if ( state.hasResolved ) {
if ( Array.isArray( data ) ) {
status = SUCCESS;
} else {
status = ERROR;
}
} else {
status = IDLE;
}

return {
...state,
records: data,
status,
hasRecords: status === SUCCESS && data?.length,
};
},
[ query, options.runIf ]
);
}
51 changes: 51 additions & 0 deletions packages/core-data/src/hooks/use-permissions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* WordPress dependencies
*/
import { useQuerySelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { IDLE, SUCCESS, RESOLVING } from './constants';
import { store as coreStore } from '../';

export default function usePermissions( resource, id ) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: for consistency, it might use a more explicit name like useEntityRecordPermissions since it works only for a single record.

What's the story for multiple records?

return useQuerySelect(
( resolve ) => {
const { canUser } = resolve( coreStore );
const create = canUser( 'create', resource );
let update, _delete;
if ( id ) {
update = canUser( 'update', resource, id );
_delete = canUser( 'delete', resource, id );
}
const isResolving =
create.isResolving ||
update?.isResolving ||
_delete?.isResolving;

const hasResolved =
create.hasResolved &&
( id ? update.hasResolved : true ) &&
( id ? _delete.hasResolved : true );

let status = '';
if ( isResolving ) {
status = RESOLVING;
} else if ( hasResolved ) {
status = SUCCESS;
} else {
status = IDLE;
}
return {
status,
isResolving,
hasResolved,
canCreate: create.data,
canUpdate: update?.data,
canDelete: _delete?.data,
Comment on lines +44 to +46
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Based on the usage in #38289, it might be convenient to assume that permissions return value value only when resolved:

Suggested change
canCreate: create.data,
canUpdate: update?.data,
canDelete: _delete?.data,
canCreate: hasResolved && create.data,
canUpdate: hasResolved && update?.data,
canDelete: hasResolved && _delete?.data,

This way you still have granular control over the status but you don't need to guard values.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this makes sense. We should assume that users don't have permission until a query is resolved.

};
},
[ resource, id ]
);
}
6 changes: 6 additions & 0 deletions packages/core-data/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import reducer from './reducer';
import * as selectors from './selectors';
import * as actions from './actions';
import * as resolvers from './resolvers';
export { default as useEntityRecords } from './hooks/use-entity-records';
export { default as useEntityRecord } from './hooks/use-entity-record';
export { default as useEntityRecordCreate } from './hooks/use-entity-record-create';
export { default as useEntityRecordUpdate } from './hooks/use-entity-record-update';
export { default as usePermissions } from './hooks/use-permissions';

import createLocksActions from './locks/actions';
import { defaultEntities, getMethodName } from './entities';
import { STORE_NAME } from './name';
Expand Down
Loading