-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Entity records hooks #38135
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
Entity records hooks #38135
Changes from all commits
dce7d36
4ec1748
baa39c2
7030cf2
461f28e
224996d
9f52bbd
4396d88
1172272
f95653b
dde78ba
64f29e3
2a41e01
1de820e
19d3d9c
ac0b646
83516cf
22462f9
43f5225
fbe31d3
2aa95ca
959d7ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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'; |
| 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, | ||
| }; | ||
| } |
| 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, | ||
| }; | ||
| } |
| 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 } | ||
| ) { | ||
| 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, | ||
| }; | ||
| } | ||
| 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 ] | ||
| ); | ||
| } |
| 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 ) { | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: for consistency, it might use a more explicit name like 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
This way you still have granular control over the status but you don't need to guard values.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ] | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
There was a problem hiding this comment.
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
runIfand how do you envision its application?