You are currently viewing Requesting data in Gutenberg with getEntityRecords

Requesting data in Gutenberg with getEntityRecords

WordPress provides a number of ways to retrieve data in Gutenbergs, one way I really like is the getEntityRecords selector. I have started using it more and more recently and find it great to use in my custom blocks.

Unfortunately, the documentation is a little sparse and there are some things to consider when working with it. However, once you start using it and its relatives, I think you’ll agree that it’s a great choice!

Retrieving data

As getEntityRecords is part of the WordPress datastore, we’ll need to import the useSelect hook to be able to access it.

import { useSelect } from '@wordpress/data';

Now that we have it available, we can make a request in our component.

const data = useSelect((select) => {
    return select('core').getEntityRecords('postType', 'post');
});

getEntityRecords can accept three parameters as defined in the official docs.

Let’s take a quick look at them now:

  • EntityType – This represents the type of data we’re requesting. i.e postType and taxonomy
  • Kind – This represent the kind of EntityType we want. i.e post and category
  • Query – This in an optional object of parameters to pass to the query. i.e { per_page: 5, parent:1234 }

Let’s look at some example queries:

// 5 most recent posts
const data = useSelect((select) => {
    return select('core').getEntityRecords('postType', 'post', { per_page: 5 });
});

// Attachments associated with a given post ID
const data = useSelect((select) => {
    return select('core').getEntityRecords('postType', 'attachment', {
        parent: 1234
    });
});

// Tags
const data = useSelect((select) => {
    return select('core').getEntityRecords('taxonomy', 'tag' );
});

OK, great! Now let’s get back to our example. We’re going to create a component that will display the most recent posts.

const Example = () => {

    // Make the data request.
    const data = useSelect((select) => {
        return select('core').getEntityRecords('postType', 'post');
    });

    // Display our list of post titles.
    return (
        <ul>
            {
                data.map(({ title: { rendered: postTitle } }) => {
                    return <li>{postTitle}</li>;
                })
             }
        </ul>
    );
};

With this is in place, we can build our block and we’re done, right?

What happened?

Let’s add a console.log to see what is happening with the data variable.

Two nulls and then an array?! What is going on?

So the issue here is that the request to the API that is being made by getEntityRecords has not been completed and so data is null until the request returns the results we asked for.

Let’s make sure we check for data before mapping.

return (
    <ul>
        {data &&
            data.map(({ title: { rendered: postTitle } }) => {
                return <li>{postTitle}</li>;
            })
         }
    </ul>
);

That will fix the error, but the user experience isn’t great. Nothing is displayed until the data Is ready.

It would be great to be able to display a loading state while we wait for the request to finish and return data. Luckily there is another selector we can leverage to get that information.

Creating a loading state

The isResolving selector is part of the core/data datastore and its whole purpose is to give us the status of a request.

It accepts three parameters that represent the query that we want to get the status of:

  • Store – This the store that we made the request to.
  • Selector – Which selector was used in the request.
  • Options – This is an array containing the parameters passed to the Selector.

Let’s set this up for our example:

// Our request
const data = useSelect((select) => {
    return select('core').getEntityRecords('postType', 'post');
});

// Set up the isLoading.
const isLoading = useSelect((select) => {
    return select('core/data').isResolving('core', 'getEntityRecords', [
        'postType', 'post'
    ]);
});

Now that we have this in place, we can use it to display a loading state for our component.

const Example = () => {
    // Request data
    const data = useSelect((select) => {
        return select('core').getEntityRecords('postType', 'post');
    });
    
    // Has the request resolved?
    const isLoading = useSelect((select) => {
        return select('core/data').isResolving('core', 'getEntityRecords', [
            'postType', 'post'
        ]);
    });

    // Show the loading state if we're still waiting.
    if (isLoading) {
        return <h3>Loading...</h3>;
    }

    // Render the post list.
    return (
        <ul>
            {data &&
                data.map(({ title: { rendered: postTitle } }) => {
                    return <li>{postTitle}</li>;
                })
             }
        </ul>
    );
};

And there we go! We are now requesting some data and have a nice loading state to show the user.

This is a pretty simple example but I’d love to see what you come up with so leave a comment with links if you have them!

Bonus: Refreshing the query

In some cases, you may have a need to refresh the request. For example, if you need an updated list of tags or uploading media. In that case, you will need to tell the datastore that the old request is no longer valid and you want a new one.

We can do that with the invalidateResolution action. To use it we’ll need to import the useDispatch hook and destructure it out from the core datastore

import { useSelect, useDispatch } from '@wordpress/data';

const { invalidateResolution } = useDispatch('core/data');

Once we have that, we can pass invalidateResolution the same parameters as isResolving and it will tell the datastore that it needs to provide a new request.

What’s really cool about this is that if a new post is added while you’re editing this one, the list will be updated without a page reload!

Here is the updated example code:

const Example = () => {
    const data = useSelect((select) => {
        return select('core').getEntityRecords('postType', 'post');
    });

    const isLoading = useSelect((select) => {
        return select('core/data').isResolving('core', 'getEntityRecords',  [
            'postType', 'post'
        ]);
     });

    const { invalidateResolution } = useDispatch('core/data');

    // Create a custom function for the button so we can trigger this on click.
    const invalidateResolver = () => {
        invalidateResolution('core', 'getEntityRecords', ['postType', 'post']);
    };

    if (isLoading) {
        return <h3>Loading...</h3>;
    }

    return (
        <div>
           <ul>
               { data &&
                     data.map(({ title: { rendered: postTitle } }) => {
                         return <li>{postTitle}</li>;
                     })
               }
           </ul>
           <button type="button" onClick={invalidateResolver}>
               Refresh list
           </button>
        </div>
    );
};

This Post Has 2 Comments

    1. Ryan

      Hi Sagar! As long as the taxonomy is exposed to the REST API, you should be able to pass it as the type – i.e. select(‘core’).getEntityRecords(‘taxonomy’, ‘NAME_OF_YOUR_TAXONOMY’ ).

Leave a Reply