Skip to content
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

Core Data: Add new useEntityRecordsWithPermissions hook #63857

Merged
merged 3 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
/**
* Internal dependencies
*/
import { unlock } from '../private-apis';
import { unlock } from '../lock-unlock';

// TODO: The following line should have been:
//
Expand Down
50 changes: 50 additions & 0 deletions packages/core-data/src/hooks/use-entity-records.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { addQueryArgs } from '@wordpress/url';
import deprecated from '@wordpress/deprecated';
import { useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
Expand All @@ -12,6 +13,7 @@ import useQuerySelect from './use-query-select';
import { store as coreStore } from '../';
import type { Options } from './use-entity-record';
import type { Status } from './constants';
import { unlock } from '../lock-unlock';

interface EntityRecordsResolution< RecordType > {
/** The requested entity record */
Expand Down Expand Up @@ -152,3 +154,51 @@ export function __experimentalUseEntityRecords(
} );
return useEntityRecords( kind, name, queryArgs, options );
}

export function useEntityRecordsWithPermissions< RecordType >(
kind: string,
name: string,
queryArgs: Record< string, unknown > = {},
options: Options = { enabled: true }
): EntityRecordsResolution< RecordType > {
const entityConfig = useSelect(
( select ) => select( coreStore ).getEntityConfig( kind, name ),
[ kind, name ]
);
const { records: data, ...ret } = useEntityRecords(
kind,
name,
queryArgs,
options
);
const ids = useMemo(
() =>
data?.map(
// @ts-ignore
( record: RecordType ) => record[ entityConfig?.key ?? 'id' ]
) ?? [],
[ data, entityConfig?.key ]
);

const permissions = useSelect(
( select ) => {
const { getEntityRecordsPermissions } = unlock(
select( coreStore )
);
return getEntityRecordsPermissions( kind, name, ids );
},
[ ids, kind, name ]
);

const dataWithPermissions = useMemo(
() =>
data?.map( ( record, index ) => ( {
// @ts-ignore
...record,
permissions: permissions[ index ],
} ) ) ?? [],
[ data, permissions ]
);
Copy link
Member

Choose a reason for hiding this comment

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

Why not do this is a single memoized selector?

Copy link
Member

Choose a reason for hiding this comment

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

I think that would also reduce the loops from 3 to 1?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that would also reduce the loops from 3 to 1?

I don't think it reduces anything as the loop will exist but within the selector. I'm not sure which is better to be honest. A huge memoized selector or smaller ones with some in between memoizations. I have a feeling that the latter is better in terms of performance but the former is easier to work with.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, but it would just be one loop, that immediately fetches the permission and sets it on the item?


return { records: dataWithPermissions, ...ret };
}
3 changes: 2 additions & 1 deletion packages/core-data/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
getMethodName,
} from './entities';
import { STORE_NAME } from './name';
import { unlock } from './private-apis';
import { unlock } from './lock-unlock';

// The entity selectors/resolvers and actions are shortcuts to their generic equivalents
// (getEntityRecord, getEntityRecords, updateEntityRecord, updateEntityRecords)
Expand Down Expand Up @@ -86,3 +86,4 @@ export * from './entity-provider';
export * from './entity-types';
export * from './fetch';
export * from './hooks';
export * from './private-apis';
10 changes: 10 additions & 0 deletions packages/core-data/src/lock-unlock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* WordPress dependencies
*/
import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis';

export const { lock, unlock } =
__dangerousOptInToUnstableAPIsOnlyForCoreModules(
'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.',
'@wordpress/core-data'
);
14 changes: 7 additions & 7 deletions packages/core-data/src/private-apis.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**
* WordPress dependencies
* Internal dependencies
*/
import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis';
import { useEntityRecordsWithPermissions } from './hooks/use-entity-records';
import { lock } from './lock-unlock';

export const { lock, unlock } =
__dangerousOptInToUnstableAPIsOnlyForCoreModules(
'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.',
'@wordpress/core-data'
);
export const privateApis = {};
lock( privateApis, {
useEntityRecordsWithPermissions,
} );
23 changes: 23 additions & 0 deletions packages/core-data/src/private-selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,26 @@ export const getBlockPatternsForPostType = createRegistrySelector(
() => [ select( STORE_NAME ).getBlockPatterns() ]
)
);

/**
* Returns the entity records permissions for the given entity record ids.
*/
export const getEntityRecordsPermissions = createRegistrySelector( ( select ) =>
createSelector(
( state: State, kind: string, name: string, ids: string[] ) => {
return ids.map( ( id ) => ( {
delete: select( STORE_NAME ).canUser( 'delete', {
kind,
name,
id,
} ),
update: select( STORE_NAME ).canUser( 'update', {
kind,
name,
id,
} ),
} ) );
},
( state ) => [ state.userPermissions ]
)
);
8 changes: 6 additions & 2 deletions packages/edit-site/src/components/post-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
* WordPress dependencies
*/
import { Button } from '@wordpress/components';
import { useEntityRecords, store as coreStore } from '@wordpress/core-data';
import {
store as coreStore,
privateApis as coreDataPrivateApis,
} from '@wordpress/core-data';
import { useState, useMemo, useCallback, useEffect } from '@wordpress/element';
import { privateApis as routerPrivateApis } from '@wordpress/router';
import { useSelect, useDispatch } from '@wordpress/data';
Expand Down Expand Up @@ -33,6 +36,7 @@ import usePostFields from '../post-fields';

const { usePostActions } = unlock( editorPrivateApis );
const { useLocation, useHistory } = unlock( routerPrivateApis );
const { useEntityRecordsWithPermissions } = unlock( coreDataPrivateApis );
const EMPTY_ARRAY = [];

function useView( postType ) {
Expand Down Expand Up @@ -199,7 +203,7 @@ export default function PostList( { postType } ) {
isResolving: isLoadingMainEntities,
totalItems,
totalPages,
} = useEntityRecords( 'postType', postType, queryArgs );
} = useEntityRecordsWithPermissions( 'postType', postType, queryArgs );

const ids = records?.map( ( record ) => getItemId( record ) ) ?? [];
const prevIds = usePrevious( ids ) ?? [];
Expand Down
Loading