| 1 |
/** |
| 2 |
* WordPress dependencies |
| 3 |
*/ |
| 4 |
// eslint-disable-next-line import/named |
| 5 |
import { store as coreStore, Taxonomy, User } from '@wordpress/core-data'; |
| 6 |
import { useSelect } from '@wordpress/data'; |
| 7 |
import { useEffect, useState } from '@wordpress/element'; |
| 8 |
|
| 9 |
/** |
| 10 |
* Internal dependencies |
| 11 |
*/ |
| 12 |
import { GutenbergFunction } from '../../../@types/gutenberg/types'; |
| 13 |
|
| 14 |
/** |
| 15 |
* Defines the states of post data as handled in the usePostData() function. |
| 16 |
* |
| 17 |
* - When `undefined`, the data is still being fetched. |
| 18 |
* - When `null`, an error has occurred before fetching the data. |
| 19 |
* - When an array, the data has been fetched and is available. |
| 20 |
* |
| 21 |
* @since 3.14.3 |
| 22 |
* @since 3.14.4 Renamed to PostData from PostDataStore. |
| 23 |
*/ |
| 24 |
interface PostData { |
| 25 |
authors: User[] | null | undefined; |
| 26 |
categories: Taxonomy[] | null | undefined; |
| 27 |
tags: Taxonomy[] | null | undefined; |
| 28 |
isReady: boolean; |
| 29 |
} |
| 30 |
|
| 31 |
/** |
| 32 |
* Hook to get the post data for the current post. |
| 33 |
* |
| 34 |
* Note: in some rare cases this function could process or return unexpected |
| 35 |
* data due to Gutenberg functions not working as expected. Consumers should |
| 36 |
* validate the data before using it. |
| 37 |
* |
| 38 |
* @since 3.14.3 |
| 39 |
* @since 3.14.4 Implemented checks to reduce risk of invalid data being processed. |
| 40 |
* @since 3.16.2 Added `isLoading` and `hasResolved` to check if the data is still being fetched or if it |
| 41 |
* has been resolved. |
| 42 |
* |
| 43 |
* @see https://github.com/Parsely/wp-parsely/issues/2423 |
| 44 |
* |
| 45 |
* @return {PostData} The post data for the current post. |
| 46 |
*/ |
| 47 |
export function usePostData(): PostData { |
| 48 |
const [ postData, setPostData ] = useState<PostData>( { |
| 49 |
authors: undefined, |
| 50 |
categories: undefined, |
| 51 |
tags: undefined, |
| 52 |
isReady: false, |
| 53 |
} ); |
| 54 |
|
| 55 |
/** |
| 56 |
* Fetches the post attributes from the editor. |
| 57 |
* This includes the author, categories, and tags, and if the data is not available, it will be set to `null`. |
| 58 |
* |
| 59 |
* @since 3.14.4 |
| 60 |
* @since 3.16.2 Added `isLoading` and `hasResolved` to check if the data is still being fetched or if it |
| 61 |
* has been resolved. |
| 62 |
*/ |
| 63 |
const postAttributes = useSelect( ( select ) => { |
| 64 |
// @ts-ignore `hasFinishedResolution` and `isResolving` are not part of the core-data store type. |
| 65 |
const { getEntityRecords, hasFinishedResolution, isResolving } = select( coreStore ); |
| 66 |
|
| 67 |
const editor = select( 'core/editor' ) as GutenbergFunction; |
| 68 |
const authorId = editor.getEditedPostAttribute( 'author' ); |
| 69 |
const categoryIds = editor.getEditedPostAttribute( 'categories' ); |
| 70 |
const tagIds = editor.getEditedPostAttribute( 'tags' ); |
| 71 |
|
| 72 |
let authorRecords: User[] | null | undefined; |
| 73 |
let categoryRecords: Taxonomy[] | null | undefined; |
| 74 |
let tagRecords: Taxonomy[] | null | undefined; |
| 75 |
|
| 76 |
if ( Number.isInteger( authorId ) ) { |
| 77 |
authorRecords = getEntityRecords( |
| 78 |
'root', 'user', { include: [ authorId ], context: 'view' } |
| 79 |
) ?? undefined; // Coalescing null to undefined |
| 80 |
} else { |
| 81 |
authorRecords = null; |
| 82 |
} |
| 83 |
|
| 84 |
if ( Array.isArray( categoryIds ) && categoryIds.length > 0 && |
| 85 |
categoryIds.every( Number.isInteger ) |
| 86 |
) { |
| 87 |
categoryRecords = getEntityRecords( |
| 88 |
'taxonomy', 'category', { include: categoryIds, context: 'view' } |
| 89 |
) ?? undefined; // Coalescing null to undefined |
| 90 |
} else { |
| 91 |
categoryRecords = null; |
| 92 |
} |
| 93 |
|
| 94 |
if ( Array.isArray( tagIds ) && tagIds.length > 0 && |
| 95 |
tagIds.every( Number.isInteger ) |
| 96 |
) { |
| 97 |
tagRecords = getEntityRecords( |
| 98 |
'taxonomy', 'post_tag', { include: tagIds, context: 'view' } |
| 99 |
) ?? undefined; // Coalescing null to undefined |
| 100 |
} else { |
| 101 |
tagRecords = null; |
| 102 |
} |
| 103 |
|
| 104 |
// Check if the data is still being fetched. |
| 105 |
const isLoading = ( |
| 106 |
isResolving( 'getEntityRecords', [ 'root', 'user', { include: [ authorId ], context: 'view' } ] ) || |
| 107 |
isResolving( 'getEntityRecords', [ 'taxonomy', 'category', { include: categoryIds, context: 'view' } ] ) || |
| 108 |
isResolving( 'getEntityRecords', [ 'taxonomy', 'post_tag', { include: tagIds, context: 'view' } ] ) |
| 109 |
); |
| 110 |
|
| 111 |
// Check if all the data has been resolved. If the data is not available, it will be set to `null`, |
| 112 |
// so it is considered resolved. |
| 113 |
const hasResolved = ( |
| 114 |
( hasFinishedResolution( 'getEntityRecords', [ 'root', 'user', { include: [ authorId ], context: 'view' } ] ) || |
| 115 |
authorRecords === null ) && |
| 116 |
( hasFinishedResolution( 'getEntityRecords', [ 'taxonomy', 'category', { include: categoryIds, context: 'view' } ] ) || |
| 117 |
categoryRecords === null ) && |
| 118 |
( hasFinishedResolution( 'getEntityRecords', [ 'taxonomy', 'post_tag', { include: tagIds, context: 'view' } ] ) || |
| 119 |
tagRecords === null ) |
| 120 |
); |
| 121 |
|
| 122 |
return { authorRecords, categoryRecords, tagRecords, isLoading, hasResolved }; |
| 123 |
}, [] ); |
| 124 |
|
| 125 |
/** |
| 126 |
* Sets the post data when all the data is ready. |
| 127 |
* This is done when all the data is fetched and resolved. |
| 128 |
* |
| 129 |
* @since 3.16.2 |
| 130 |
*/ |
| 131 |
useEffect( () => { |
| 132 |
const { |
| 133 |
authorRecords, |
| 134 |
categoryRecords, |
| 135 |
tagRecords, |
| 136 |
isLoading, |
| 137 |
hasResolved, |
| 138 |
} = postAttributes; |
| 139 |
|
| 140 |
const isPostDataReady: boolean = hasResolved && ! isLoading; |
| 141 |
|
| 142 |
if ( isPostDataReady ) { |
| 143 |
setPostData( { |
| 144 |
authors: authorRecords, |
| 145 |
categories: categoryRecords, |
| 146 |
tags: tagRecords, |
| 147 |
isReady: true, |
| 148 |
} ); |
| 149 |
} |
| 150 |
}, [ postAttributes ] ); |
| 151 |
|
| 152 |
return postData; |
| 153 |
} |
| 154 |
|