In order to accomplish this, I created a custom data store and extracted the available meta keys from the routes
object returned when hitting the wp-json/wp/v2/
endpoint in the editor.
This approach decreased the number of API requests compared to that with which I started. The initial approach in the question would have made one call per block vs one per page when editing.
Disclaimer: There is probably a cleaner way than using the RegEx below to extract the meta fields from the routes, but it is a start.
Usage with Custom Data Store:
const metaFields = useSelect(
(select) => select('example-meta/data').getMetaFields(),
[]
);
Custom Data Store Example:
import { createReduxStore, register } from '@wordpress/data';
import apiFetch from '@wordpress/api-fetch';
import { addQueryArgs } from '@wordpress/url';
const STORE_NAME = 'example-meta/data';
const DEFAULT_STATE = {
metaFields: null,
};
const actions = {
fetchFromAPI() {
return {
type: 'FETCH_META_FROM_API',
};
},
setMetaFields(metaFields) {
return {
type: 'SET_META_FIELDS',
data: metaFields,
};
},
};
const getMetaFieldsFromRoutes = (routes) => {
const metaFields = {};
const routePaths = Object.keys(routes);
const pathRegEx = new RegExp(/\/wp\/v2\/([A-Za-z0-9-_]+)$/);
const pathsToCheck = routePaths.filter((path) => pathRegEx.test(path));
pathsToCheck.forEach((path) => {
const { endpoints } = routes[path];
if (endpoints) {
endpoints.forEach((endpoint) => {
if (endpoint?.args?.meta?.properties) {
const endpointMetaKeys = Object.keys(
endpoint.args.meta.properties
);
endpointMetaKeys.forEach((metaKey) => {
if (!metaFields.hasOwnProperty(metaKey)) {
metaFields[metaKey] =
endpoint.args.meta.properties[metaKey];
}
});
}
});
}
});
return metaFields;
};
const store = createReduxStore(STORE_NAME, {
reducer(state = DEFAULT_STATE, action) {
switch (action.type) {
case 'SET_META_FIELDS':
return {
...state,
metaFields: action.data,
};
}
return state;
},
actions,
selectors: {
getMetaFields(state) {
const { metaFields } = state;
return metaFields;
},
},
controls: {
FETCH_META_FROM_API() {
return apiFetch({
path: addQueryArgs(`/`, {
_fields: 'routes',
}),
parse: true,
});
},
},
resolvers: {
*getMetaFields() {
const result = yield actions.fetchFromAPI();
const { routes } = result;
const metaFields = getMetaFieldsFromRoutes(routes);
return actions.setMetaFields(metaFields);
},
},
});
register(store);