Building Dynamic Blocks for the Gutenberg Editor in WordPress

Reading Time: 5 minutes

Update: 11/08/2018: I’ve updated this guide to use withSelect for making a REST API call instead of the deprecated withAPIData. I’ve also got the sample blocks up on github. These aren’t blocks I would recommend for production. But you can fork and fiddle with them to learn. This one is API Block.

Now that we’ve gone over how the edit and save functions work with the new Gutenberg editor, we know that it’s easy to save custom markup for our block. Throw in the RichText component and we can create easily editable blocks.

But what if we want to build a block that dynamically displays data from the database? That’s where dynamic blocks come into play. And for that we’ll turn back to our trusty old friend, PHP.

When are Dynamic Blocks with PHP Useful

Using PHP to create dynamic blocks is needed if you’re working with a block that needs to update data without saving a post.

Let’s say for example we want to pull in recent posts. If we’re just using the save function with JavaScript out saved markup will be the most recent posts from the last time the current post was updated. If we add a post in the future it won’t know to update the markup.

With server-side rendering through PHP we can tell the block to reach out to the database to pull in the most recent posts when the block is rendered to the user.

The Rules of Dynamic Blocks

There are a few considerations to keep in mind when using dynamic blocks.

You should still make the edit function a representation of how the block will be displayed on the frontend. This is made easier by using the REST API or the <ServerSideRender> component. More on both in a bit.

The save function can either return null which forces the content to be rendered server-side every time. This is perfectly acceptable for more blocks.

Alternatively you can make the save function a cached version of the block that can be displayed if the plugin containing the block is deactivated. Essentially you’re saving some form of fallback markup for the block to use in the case the server-side rendering function goes away.

Creating the edit Screen with the REST API

You’ll still be calling registerBlockType in your JavaScript. With the only caveat being that if you’re using attributes, they’ll be defined in PHP instead of JavaScript. You will still be able to set those attributes with setAttribute though. More on that in a minute.

In the case of a latest posts block or other blocks accessing information form the database we can take advantage of the REST API using a handy withSelect that we can access from the wp.data package.

Update: Now with withSelect goodness! This article previously used withAPIData that has since been deprecated.

edit: withSelect(select => {
    return {
        posts: select('core').getEntityRecords('postType', 'post', {
            per_page: 3
        })
    };
})(({ posts, className, attributes, setAttributes }) => {
    if (!posts) {
         return (
             // What to output while loading
         )
    }
    if (0 === posts.length) {
        return (
            // What to return if no posts are found
        )
    }
    return (
        // What to return if posts are found
    );
} // end withSelect
), // end edit

To make that a little simpler what we essentially have is withSelect( // REST API call )( // function to run with returned API data )

So in the first portion we’re simply running the withSelect function to get the 3 most recent results from our posts endpoint.

edit: withSelect(select => {
    return {
        posts: select('core').getEntityRecords('postType', 'post', {
            per_page: 3
        })
    };
})

Note: That’s not valid code. Don’t copy only that.

Then the second part is essentially our callback function that we want to run with the data to output our preferred markup. We’re returning different markup in a few situations here:

  1. While the API is loading posts
  2. If the API returns no results
  3. When the API returns results
(({ posts, className, attributes, setAttributes }) => {
    if (!posts) {
         return (
             // What to output while loading
         )
    }
    if (0 === posts.length) {
        return (
            // What to return if no posts are found
        )
    }
    return (
        // What to return if posts are found
    );
} // end withSelect
), // end edit

In each of these return statements we can make use of any attributes we may have used in the past. And even use custom components like <RichText> to output user entered data. But we’ll need to register the custom attributes through PHP.

First here’s an example of fully functional edit function for outputting 3 recent posts along with a <RichText> title.

edit: withSelect(select => {
    return {
        posts: select('core').getEntityRecords('postType', 'post', {
            per_page: 3
        })
    };
})(({ posts, className, attributes, setAttributes }) => {
    if (!posts) {
        return <p className={className}>
            <Spinner />
            {__('Loading Posts')}
        </p>;
    }
    if (0 === posts.length) {
        return <p>{__('No Posts')}</p>;
    }
    return (
        <div className={className}>
            <RichText
                tagName="h2"
                value={attributes.content}
                onChange={content => setAttributes({ content })}
                placeholder={__('What up, bruh?')}
            />
            <ul>
                {posts.map(post => {
                    return (
                        <li>
                            <a className={className} href={post.link}>
                                {post.title.rendered}
                            </a>
                        </li>
                    );
                })}
            </ul>
        </div>
    );
} // end withSelect
), // end edit

Rendering the Block Server Side with PHP

In order for a block to have a server component for display we need to make use of the render_callbak on the register_block_type function.

Not to be confused with the JavaScript function registerBlockType, the PHP function register_block_type is hooked into the init action.

register_block_type takes two arguments, the namespace/block-name and an object for several arguments including attributes and render_callback which we’ll be using.

Let’s take a look at this in action.

function register_api_blocks() {
    register_block_type(
        'cgb/api-block',
        array(
            'attributes' => array(
                'content' => array(
                    'type' => 'string',
                ),
                'className' => array(
                    'type' => 'string',
                ),
            ),
            'render_callback' => 'cgb_api_block_posts',
        )
    );
}

add_action('init', 'register_api_blocks');

In our JavaScript edit function above we added a <RichText> component for setting a content attribute as an <h2> for our latest posts list.

Since we’ll need to access that attribute with our PHP function we need to define the attribute in our register_block_type call. We’re doing that on lines 5-11 above. When defining the attributes in PHP we don’t want to define them in JavaScript as well.

Defining attributes in PHP allows for the $attributes to be passed to our render_callback function. Which we’re setting with 'render_callback' => 'cgb_api_block_posts',.

Next we need to define the cgb_api_block_posts function which will make our dynamic call to the database and return our posts.

function cgb_api_block_posts( $attributes ) {
    $block_content = "This displays on the frontend";
    // Return the frontend output for our block 
    return $block_content;
}

This is a short example that shows our block outputting “This displays on the frontend”. Not super useful at this point, but this is just to illustrate how the callback returns content.

Let’s take a look at a full example that gets our 3 most recent posts, uses our content attribute, and outputs it all in some nice markup.

function cgb_api_block_posts( $attributes ) {
    // get our 3 most recent posts using wp_get_recent_posts
    $recent_posts = wp_get_recent_posts(
        array(
            'numberposts' => 3,
            'post_stats'  => 'publish',
        )
    );

    // If our content attribute is set output it within an h2 element
    $dynamic_block_title = ( $attributes['content'] ) ? sprintf( '<h2>%1$s</h2>', $attributes['content'] ) : '';

    $list_item_markup = '';

    // Put together markup for each post to output
    foreach ( $recent_posts as $post ) {
        $post_id = $post['ID'];

        $title = get_the_title( $post_id );

        $list_item_markup .= sprintf(
            '<li><a href="%1$s">%2$s</a></li>',
            esc_url( get_permalink( $post_id ) ),
            esc_html( $title )
        );
    }

    // If our className attribute is set output it in addition to a class set in our PHP
    $class = 'cgb-api-block';
    if ( isset( $attributes['className'] ) ) {
        $class .= ' ' . $attributes['className'];
    }

    // Built out our final output
    $block_content = sprintf(
        '<div class="%1$s">%2$s<ul>%3$s</ul></div>',
        esc_attr( $class ),
        $dynamic_block_title,
        $list_item_markup
    );

    return $block_content;
}

Rather than break that down I’ve added comments to the code to explain what each portion of the code is doing.

Bonus: Rendering the edit Screen Server Side 🤯

One of the newer features of Gutenberg (since 2.8) is the addition of the <ServerSideRender> component that simplified creating the editor representation for dynamic blocks.

This component allows the editor to render the block based on the PHP render_callback function.

You’ll need to make sure to include the ServerSideRender component from the wp.components library to use it.

That will look like this at the top of your block.js file.

const { ServerSideRender } = wp.components;

Implementing the <ServerSideRender> component in our edit function then looks like this:

edit: function ({ attributes, setAttributes, className, isSelected }) {
    return (
        <ServerSideRender
            block="cgb/block-dynamic-block"
            attributes={attributes}
        />
    );
},

Wrapping Up

In order to create blocks with dynamic data make use of the PHP register_block_type function with a render_callback. You can then render your blocks in the edit screen using the <ServerSideRender> component or make use of the Rest API using withSelect.

With these tools you can start building some seriously awesome blocks.

Pin It on Pinterest