6

I'm using React select so that I can create a block option that will give me the option of selecting post types and posts based on the selected post types. Basically, a query block that is in the works in Gutenberg plugin if I'm not mistaken.

enter image description here

Now the thing that's bothering me the most is the fact that I cannot find out the best way to fetch all the posts from the selected options.

For instance I can fetch all the allowed post types like this:

    const postTypeOptions = useSelect((select) => {
        const { getPostTypes } = select('core');

        const items = getPostTypes() ?? [];

        const data = items.filter((element) => manifest.allowed.postTypes.find((item) => element.slug === item)) ?? [];

        return data.map((item) => {
            return {
                label: item.labels.name,
                value: item.slug,
                taxonomies: item.taxonomies,
            };
        }) ?? [];
    });

I'm inside the options part for my block, and I have a manifest.json, where I can filter allowed post types (I don't need all the post types available for me).

What the above code will do is, when I select my block, it will do an API fetch to get all the post types using getPostTypes(), which is in my case equivalent of wp.data.select('core').getPostTypes().

Now, fetching things like posts can be done using getEntityRecords() like

wp.data.select( 'core' ).getEntityRecords( 'postType', 'post' )

Which will give me posts in my WP. I can replace the post for any custom post type to fetch that custom post type posts.

But the problem is I cannot get all the posts from multiple post types.

The reason is that underneath this, the getEntityRecords is pinging the API endpoint, and there is no way to retrieve all the posts from one endpoint from multiple post types in one go.

So how do I solve this? Create a custom endpoint that will return all the posts based on what I pass as arguments of the endpoint? Doing fetch in the useSelect is not great, as that will trigger every time I touch the block. Using stuff from wp.data.select uses caching and React store to avoid that I guess.

Any pointers into how to achieve that would be super helpful.

4
  • 1
    Why not just do one getEntityRecords call per post type?
    – kero
    Commented Feb 10, 2021 at 17:29
  • I tried looping it, but I got some mapSelect error. The calls inside the useSelect gets called multiple times (not sure why), first it returns empty array, then after some time I get the filled array of posts.
    – dingo_d
    Commented Feb 11, 2021 at 7:49
  • @dingo_d can you add that code so we can debug it?
    – Tom J Nowell
    Commented Feb 11, 2021 at 12:38
  • @TomJNowell it was the below code but without all the checks for empty values and the isResolving check, so when I tried to return the item.title.rendered it threw an error about it being undefined and that broke things.
    – dingo_d
    Commented Feb 11, 2021 at 13:16

2 Answers 2

6

Ok, after some research I got the result I needed:

// Fetch all posts based on the selected postType.
const postsOptions = useSelect((select) => {
    const { getEntityRecords } = select('core');
    const { isResolving } = select('core/data');

    const postTypeSlugs = [...postType].map((element) => element.value) ?? [];

    if (!postTypeSlugs.length) {
        return [
            {
                label: __('No Filter used', 'slug'),
                value: '',
            }
        ]
    }

    const postList = [];

    postTypeSlugs.forEach((postType) => {
        const args = ['postType', postType, {per_page: -1}];

        if (!isResolving('core', 'getEntityRecords', args)) {
            const result = getEntityRecords('postType', postType, {per_page: -1});

            if (result !== null) {
                postList.push(result);
            }
        }
    });

    if (typeof(postList[0]) !== 'undefined') {
        return [
            {
                label: __('No Filter used', 'slug'),
                value: '',
            },
            ...postList[0].map((item) => {
                if (isEmpty(item)) {
                    return {};
                } else {
                    return {
                        label: item.title.rendered || '',
                        value: item.id || '',
                    };
                }
            }),
        ];
    }
});

Plus a helper from here, that checks if the array is not empty

const isEmpty = a => Array.isArray(a) && a.every(isEmpty);

This seems to be working. Not pretty, but does the job (no extra api calls as far as I can see).

2

Since while I was doing research I stumbled accross this thread, but it didn't bring me to the result I wanted. So I decided to register here to point out my approach to aquire the need of fetching posts with multiple post types.

One thing is that the restAPI just does not accept multiple post type natively. I assume thats why getEntityRecords also does not provide that capability.

1st) I registered a new RestAPI Endpoint to get the ability to query multiple post types when using it:

//PHP
add_action( 'rest_post_query', function( $args, $request ){
    $post_types = $request->get_param( 'type' );
    if( ! empty( $post_types ) ){
        if( is_string( $post_types ) ){
            // filtering posttypes, seperated with comma into an array, ignoring square brackets
            $post_types = explode( ',', str_replace( array( '[', ']') , '', $post_types ) );
            foreach ( $post_types as $key => $post_type ){
                $object =  get_post_type_object( $post_type );
                if( ! $object || ! $object->show_in_rest   ){
                    unset( $post_types[ $key ] );
                }
            }
        }
        $args[ 'post_type' ] = $post_types;
    } else {
        // fallback: no type defined, return post as default
        $args[ 'post_type' ] = 'post';
    }
    return $args;
}, 10, 2 );

Check your new API endpoint easily by calling it with your browser

https://*yoururl.com*/wp-json/wp/v2/posts?per_page=10&categories=0&type=posts,page

I don't use the Gutenberg function getEntityRecords anymore to fetch posts. I choose that way performance wise to avoid multiple calls of getEntityRecords.

2nd) In my Gutenberg block I created a hook to call:

export default function useFetchPosts( url ) {
    const [data, setData] = useState( null );
    useEffect(() => {
        async function loadData() {
            const response = await fetch( url );
            if(! response.ok ) {
                return false;
            }
            const posts = await response.json();
            setData(posts);
        }
        loadData();
    }, [url]);
    return data;
}

3rd) to get the posts I just call it like this inside my edit block:

// parsedCategories and
// parsedPostTypes  have to set and parsed by you before ofcourse ;)
const numberposts = 10;
const postFields = '&_fields=id,author,link,title,_links';
const posts = useFetchPosts( 'https://'+window.location.host+'/wp-json/wp/v2/posts?per_page='+numberposts+'&categories='+parsedCategories+'&_embed=author&type='+parsedPostTypes+'&orderby=date&order=desc'+postFields );

Check out the restAPI reference for available args and parms: https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/

Cheers

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.