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:
- While the API is loading posts
- If the API returns no results
- 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.