The WordPress Gutenberg method wp.data.withSelect allows us to wrap a component in a React Higher Order Component, which adds functionality or adds data to the props of the wrapped component. But sometimes, all we need is a simpler method of just fetching some data — e.g. posts — from the REST API and then rendering the component using these posts.

withSelect and HOC is very powerful, but there’s an easier way: the useSelect method. We can insert this method call directly in our component and render the component depending on whether the data is available or not. This makes the code much leaner and much more maintainable for developers with less experience. This solution is also a great deal easier to copy and paste for reuse across many components.

I’ve used the technique in the edit component of a WordPress Gutenberg Block today, in which I need to create a SelectControl to allow the user to select a page. (I’ve since modified the interface to output a TreeSelect component instead of a SelectControl, but that adds more complexity that is unnecessary for this blog post.)

import { InspectorControls } from "@wordpress/block-editor";
import { Fragment, PanelBody, SelectControl, Spinner } from "@wordpress/components";
import { useSelect } from "@wordpress/data";
import { _x } from "@wordpress/i18n";

const Edit = props => {
    const { attributes, setAttributes } = props;

    const { post_id } = attributes;

    // The value of 'posts' will be false until the REST API responds asynchronously
    const { posts } = useSelect(select => {
        return {
            posts: select("core").getEntityRecords("postType", "page", {
                per_page: 100,
                order: "asc",
                order_by: "menu_order",
            }),
        };
    });

    const options = [];

    // Make an array from the REST API response if posts are available
    if (!!posts) {
        Object.values(posts).forEach(post => {
            options.push({
                value: post.id,
                label: post.title.rendered ? post.title.rendered : _x('No title', 'SelectControl option label', 'sha'),
            });
        });
    }

    return (
        <Fragment>
            <InspectorControls>
                <PanelBody title={_x("Settings", "PanelBody title", "sha")}>
                    {!posts && <Spinner />}
                    {!!posts && (
                        <SelectControl
                            label={_x(
                                "Select a page",
                                "SelectControl label",
                                "sha"
                            )}
                            value={post_id}
                            options={options}
                            onChange={post_id => setAttributes({ post_id })}
                        />
                    )}
                </PanelBody>
            </InspectorControls>
        </Fragment>
    );
};

export default Edit;

Addendum 6th February: if you’re only getting a single dataset, you can simplify the usage of useSelect in the example above by directly assigning the result of the API call to the posts constant.

const posts = useSelect(select => {
    return select("core").getEntityRecords("postType", "page", {
        per_page: 100,
        order: "asc",
        order_by: "menu_order",
    }),
});

Addendum 29th July: if you’re also looking to update data on the server using this non-TOC method, then you can use the editPost method from useDispatch in a similar way. (In the example, the handleMetaValueChange callback would be used when e.g. a SelectControl change event is fired.)

const { editPost } = useDispatch('core/editor');

const handleMetaValueChange = (main_offset) => {
	editPost({ meta: { main_offset } });
};

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.