Create Custom Gutenberg Block – Part 9: Dynamic Blocks and PHP Render

So far we have rendered the block’s output in Javascript. However with dynamic content like recent posts or displaying a post we need to render the block’s output in PHP. In this post we’ll learn how, and why.

Why do we need to handle dynamic blocks differently?

Some examples are obvious; a block that displays the latest posts in a category is dynamic because the posts will change over time. You don’t choose the posts in the block; instead you’ll probably have settings for choosing category, what information to show for each posts, the number of posts, the number of columns, and so on. Every time WordPress loads a post with this block it needs to query WordPress at that time for the newest posts. Viewing the same post the month after might yield different results even though the post with the block itself has not been updated.

But the necessity of dynamic blocks are sometimes not all that obvious. You might imagine a featured post block where you choose one specific post to display it, is not necessarily dynamic. But it could be – and should be. Remember that the selected post could update its title, excerpt or featured image at any time – and that should be reflected in the blocks that features this post.

In order to make a non-dynamic block for showing a single post, you’ll need to save the post ID, its URL, featured image URL, excerpt string, or whatever you need to preview the post, in the block’s attributes. And herein lies the problem. If you update the selected post’s featured image, the post with the featured post block will not automatically update its attributes. It will stay saved with the old featured image’s URL. Only when you edit the post with the block, and make sure it re-saves the attributes with the updated information, the block will show the correct updated information.

So whenever we deal with dynamic content – posts, categories, or anything that might change over time – we are dealing with dynamic blocks. And for dynamic blocks we need to use PHP – server side code – to render our block to ensure that it will retrieve updated information every time it renders.

The changed nature of dynamic blocks in the editor

When you start developing dynamic blocks the nature of your block within the editor will change. Your block’s edit function often needs to be dynamic as well. For example for a featured post block you’ll need to fetch posts to choose from, or for a latest news block you’ll need to retrieve a list of categories for the user to choose from.

It is fully possible to request information from WordPress from within the editor by doing AJAX requests – either using packages and components or performing them manually with WordPress REST API. Regardless of the method you go for your block needs to handle the async stream of input – the timeframe while waiting for a response.

There are multiple methods and patterns to create a dynamic block for Gutenberg. Most commonly you’ll use a React pattern called higher-order components in which WordPress provides plenty of functions and components for.

We’ll look at how to fetch posts and how to retrieve categories in our block in the next tutorial part. For now we need to learn how to make PHP render our block.

Making our block ready for PHP rendering

The main part we need to do in Javascript is making the block’s save function return null. You could keep the original output be, but once we tell WordPress to use PHP for rendering the block, this will be ignored. To make it clear for ourselves and others that the block’s output is not handled in Javascript we’ll change the save function.

registerBlockType('awp/firstblock', {
	title: __('My first block', 'awhitepixel'), 
	category: 'common',
	...
	edit: FirstBlockEdit,
	save: () => { return null }
});

Don’t forget that changing the save function will make all existing blocks go broken. Re-add the block. The block should work just like before; with the settings and updating the attributes. It will now just not output anything in frontend. The comment block will be there, storing all attributes in JSON, but no visible HTML is rendered.

However; if any of your attributes are using the source property, you need to change this. This is not supported with blocks that is rendered with PHP because they are parsed directly from the save’s output – which we now return null on. We use source on the second RichText in our block – for the paragraph. At this point the editor won’t save what you put in this RichText at all.

So if you also still are using source on the myRichText attribute, we need to remove the source and selector properties to ensure that the attributes will be stored and not parsed from the save function:

attributes: {
	...
	myRichText: {
		type: 'string',
	},
	...

After this our block is ready for rendering in PHP. We can leave the Javascript files (don’t forget to build it) and the rest is done in PHP.

Rendering blocks in PHP

In order to tell WordPress to render the block’s output in PHP we add a new argument to the function register_block_type(). We need to add the key render_callback to the array with a value of the function it should run.

functions.php
add_action('init', function() {
	register_block_type('awp/firstblock', [
		'editor_script' => 'awp-myfirstblock-js',
		'render_callback' => 'awp_myfirstblock_render'
	]);
});

The PHP render function

Let’s define the function awp_myfirstblock_render further down in functions.php (or wherever you’ve put your PHP code). Our function will get two parameters; we’ll call them $attr and $content.

functions.php
function awp_myfirstblock_render($attr, $content) {
	// return the block's output here
}

The parameter $attr will be an associative array with all saved attributes. The second parameter, $content, is for blocks inside our block. This is only relevant for blocks that support nested blocks – which for example Columns or Cover do.

The function should never echo anything out. All output must be returned so you need to build a string and return it at the end.

Something important to remember about attributes is that only saved attributes will appear in the first parameter to the function. If an attribute was never actually changed and saved – ie just relying on its default, the attribute will not be included at all for our PHP function.

You need to handle this issue with either always checking if (isset($attr['...'])) or the preferable way: defining the attributes in our register_block_type() call. We can provide another key, attributes, and set its value to an array with all attributes we wish to extract from our block. The structure should be identical to the one you defined in Javascript, but instead of a Javascript object you need it in a PHP array. This can be a little cumbersome to redefine the same attributes, but with a smart code-editor it should be pretty quick to copy+paste and multi-line-edit it into PHP.

Adding the attributes for our render function

Let’s add the new attributes element to register_block_type() and paste in the exact same attributes we defined in our Javascript file:

functions.php
add_action('init', function() {
	register_block_type('awp/firstblock', [
		'editor_script' => 'awp-myfirstblock-js',
		'render_callback' => 'awp_myfirstblock_render',
		'attributes' => [
			'myRichHeading' => [
				'type' => 'string'
			],
			'myRichText' => [
				'type' => 'string'
			],
			'textAlignment' => [
				'type' => 'string',
			],
			'toggle' => [
				'type' => 'boolean',
				'default' => true
			],
			'favoriteAnimal' => [
				'type' => 'string',
				'default' => 'dogs'
			],
			'favoriteColor' ==> [
				'type' => 'string',
				'default' => '#DDDDDD'
			],
			'activateLasers' => [
				'type' => 'boolean',
				'default' => false
			],
		]
	]);
});

function awp_myfirstblock_render($attr, $content) {
	return '<div>'.$attr['favoriteColor'].'</div>';
}

Keep in mind that if you define a default for all attributes, your function’s $attr parameter should always contain all attributes. For example the attribute textAlignment above will only exist in $attr if it was changed – and you’ll need to check isset($attr['textAlignment']).

Unfortunately, at the moment, there’s two things you won’t get ahold of with PHP block render. One is the className prop – so you need to build the wrapping class (if you want it) yourself. The other is the support property for block alignment. At the moment WordPress is not supporting this property in dynamic blocks. We won’t get ahold of the value of the chosen block alignment unless we change it into an attribute and handle it manually in Javascript.

As for the HTML output of the function this is fully up to you!

Requesting PHP render from inside editor

It is possible to request the PHP render of our block inside the editor. This is useful if you want to be able to preview the block’s output in the editor. We can do this with a component called ServerSideRender from the wp.editor package.

As props to ServerSideRender you need to define all attributes you wish to pass on. As minimum you need to provide the block’s name to the prop block, so that WordPress knows which render method to look for. This is available for you in props.name to the edit function. You also need to provide any attributes you need in the prop attributes. If you want, you can add custom variables outside attributes here as well. Just keep in mind that this will only work for inside editor, and not frontend.

You cannot use ServerSideRender in the block’s save function. Your save function must still return null.

Let’s implement ServerSideRender in our block to see it in practice.

Using ServerSideRender for block preview/edit mode

If you followed the previous step where we made a preview/edit mode toggler for our block, we can now use ServerSideRender to render the preview of the block from PHP when we toggle to preview mode.

First we need to remember to destructure ServerSideRender at the top:

const { ServerSideRender } = wp.editor;

If you remember from previous step we used the components Disabled and/or Placeholder to hold the preview. The problem with using Placeholder is that we get unwanted styling applied to our output. Let’s replace Placeholder with ServerSideRender. You can choose to keep the Disabled component, to ensure that none of its content will be clickable or draggable.

At the code for rendering the block when the attribute editMode is false, we do:

...
{!this.state.editMode && 
	<ServerSideRender
		block={this.props.name}
		attributes={{ 
			myRichHeading: attributes.myRichHeading, 
			myRichText: attributes.myRichText, 
			textAlignment: attributes.textAlignment, 
			toggle: attributes.toggle, 
			favoriteAnimal: attributes.favoriteAnimal, 
			favoriteColor: attributes.favoriteColor,
			activateLasers: attributes.activateLasers
		}}
	/>
}
...

Now our custom button in the toolbar will render the output from PHP when we switch to preview mode. The output should be identical when viewing the post in frontend. This is a good habit to ensure that the output is identical in both editor and frontend.

Example: Dynamic block showing a selected post

The output in your PHP render function can be anything at all and you have full access to all WordPress functions. Assume a block where a post ID will be saved in an attribute. In the render_callback PHP function you can query the post from the ID and output its information. It should be pretty self-explanatory how to do this, but here is a quick example.

NB: In this example we’ll simply add a text input to the editor for manually entering a post ID. This is not a very intuitive and user-friendly solution for selecting a post – but this is what we’ll learn in the next step. The focus here is on the PHP part of rendering the selected post.

Let’s add an attribute selectedPostId of type number:

attributes: {
	selectedPostId: {
		type: 'number'
	}
}

And somewhere inside our block’s edit function we add a TextControl component. It can be wherever you want – inside the block or in the Inspector.

<TextControl 
	label={__("Type in post ID", 'awhitepixel')}
	type="number"
	value={attributes.selectedPostId}
	onChange={(newval) => setAttributes({ selectedPostId: parseInt(newval) })}
/>

Note that I’m taking extra care to ensure that the input saves the attribute correctly as a number by converting it to integer with parseInt(). Even though we set type prop type to number in order to render a number input, the changed value is still interpreted as a string. WordPress won’t save your attribute if it’s in the wrong format.

Don’t forget to add the new attribute to your ServerSideRender component if you have one:

<ServerSideRender
	block={this.props.name}
	attributes={{ 
		selectedPostId: attributes.selectedPostId,
		...

The PHP part

That should’ve taken care of the Javascript part. Let’s move on to PHP. First we need to add the new attribute selectedPostId to the attributes array in register_block_type():

register_block_type('awp/firstblock', [
	'editor_script' => 'awp-myfirstblock-js',
	'render_callback' => 'awp_myfirstblock_render',
	'attributes' => [
		'selectedPostId' => [
			'type' => 'number',
			'default' => 0
		],
		...
	]
]);

In the render_callback function we can now access the post ID with $attr['selectedPostId']. We can with that perform a simple get_post() and output the post’s data; its link and title:

function awp_myfirstblock_render($attr, $content) {
	$str = '';
	if ($attr['selectedPostId'] > 0) {
		$post = get_post($attr['selectedPostId']);
		if (!$post) {
			return $str;
		}
		$str = '<div class="awp-myfirstblock">';
		$str .= '<a href="' . get_the_permalink($post) . '">';
		$str .= '<h3>' . get_the_title($post) . '</h3>';
		$str .= '</a>';
		$str .= '</div>';
	}
	return $str;
}

This is a very basic example meant as a spring board for you to write more advanced and custom code.

Now that we know how to handle rendering dynamic blocks, the next step is to learn how to make the editor part more intuitive as well. In the next step we’ll focus on how to query posts from within the block editor and providing the user a better way of selecting a post.

One comment on “Create Custom Gutenberg Block – Part 9: Dynamic Blocks and PHP Render”

Leave a comment