How to Create Dynamic Blocks for Gutenberg

Are you still puzzled by Gutenberg? Or are you among those who firmly believe in the potential of the block editor and want to find out how far can push their creativity using the block editor?

Whatever category of users you fall into, Gutenberg is here to stay and this post will give you an in-depth overview of what goes on behind the scenes of the WordPress block editor. But that’s not all!

Following our previous tutorial where we provided a general introduction to Gutenberg block development, this article goes beyond the basics, introducing more advanced block types. These blocks are referred to as dynamic blocks.

Today you’ll learn what dynamic blocks are, how they work, and all that you need to know to create dynamic blocks from scratch.

So, what are Gutenberg dynamic blocks, and what are the key differences between static and dynamic blocks?

What Are Dynamic Blocks? An Example

While with static blocks the content is manually added by the user while editing a post or page, with dynamic blocks the content is loaded and processed on the fly on page load. With dynamic blocs, the block content is picked up from the database and displayed as is or resulting from any kind of data manipulation.

Let’s explain that with an example. Say you want to create a group of nested blocks showing post author details with a selection of the latest posts from the same author.

A Group block including Post Author and Latest Posts
A Group block including Post Author and Latest Posts

As Gutenberg users, you could use the following blocks:

  • The Heading core block
  • The Post Author core block
  • The Latest Posts core block

You could also create a group including those blocks and add the group to reusable blocks for future use.

Adding a group block to reusable blocks
Adding a group block to reusable blocks

It’s quite straightforward, isn’t it? You can create a dynamic block and add it to your posts and pages in a snap.

As of WordPress 5.9, the block editor provides more than 90 different blocks, and chances are that you’ll find the block that’s right for you just out of the box. And, if you’d need more, run a quick search in the WordPress plugin directory and you’ll find a lot of free plugins providing additional blocks.

But what if you’re a WordPress developer – or you are planning a career as a WordPress developer? Perhaps you have very specific needs and can’t find the block you are searching for, or you simply want to gain new professional skills. In such situations, you may want to learn how to create your dynamic blocks.

Ready to take your career as WordPress developer to the moon? ?? Start with this massive guide to dynamic block development! ?‍?Click to Tweet

Gutenberg Dynamic Blocks from a Developer’s Perspective

Dynamic blocks have two main use cases.

The first use case is when you need to update a block’s content when the page containing the block has not been updated. For example, this happens when the block includes a list of the latest posts or comments, and in general whenever the content of the block is dynamically generated using data retrieved from the database.

Adding a Query Loop block
Adding a Query Loop block

The second use case is when an update to the block code needs to be immediately shown on the front end. Using a dynamic block instead of a static block causes the changes to be immediately applied to all occurrences of the block.

On the other hand, if you’d change the HTML produced by a static block, the user will see an invalidation dialog until every single instance of the previous version of the block is removed and replaced with the new version, or you mark the old version as deprecated (see also Deprecation and Block Validation, Deprecation and Migration Experience).

Unexpected or invalid content.
Unexpected or invalid content.

That being said, there are a few concepts you need to understand before you can start building dynamic blocks.

Application State and Data Stores

Gutenberg is a React SPA application, and everything in Gutenberg is a React component. Post title, headers, paragraphs, images and any block of HTML content in the editor is a React component, as well as sidebar and block toolbar controls.

In our previous article, we only used properties to store data. In this article, we’ll take it a step further by introducing the concept of state.

To put it simply, the state object is a plain JavaScript object used to contain information about a component. The state of the component can change over time, and any time it changes, the component re-renders.

Similarly to the state object, properties are plain JavaScript objects used to hold information about the component. But there’s a key difference between props and state:

props get passed to the component (similar to function parameters) whereas state is managed within the component (similar to variables declared within a function).

You may think of the state as a snapshot of data taken at a given point in time that an application stores to control a component’s behavior. For example, if the block editor settings sidebar is open, a piece of information will be stored somewhere in the state object.

When the information is shared within a single component, we call it local state. When the information is shared across components within an application, we call it Application State.

Application State is closely related to the concept of store. According to the Redux docs:

A store holds the whole state tree of your application. The only way to change the state inside it is to dispatch an action on it.

So, Redux stores an application state in a single immutable object tree (namely a store). The object tree can only be changed by creating a new object using actions and reducers.

In WordPress, stores are managed by the WordPress data module.

Modularity, Packages, and Data Stores in Gutenberg

The Gutenberg repository is built from the ground up on several reusable and independent modules that, combined together, build the editing interface. These modules are also called packages.

The official documentation lists two different types of packages:

  • Production packages make up the production code that runs in the browser. There are two types of production packages in WordPress:
    • Packages with stylesheets provide stylesheets to function properly.
    • Packages with data stores define data stores to handle their state. Packages with data stores can be used by third-party plugins and themes to retrieve and manipulate data.
  • Development packages are used in development mode. Those packages include tools for linting, testing, building, etc.

Here we are mostly interested in packages with data stores, used to retrieve and manipulate data.

The WordPress Data Store

The WordPress data module is built upon Redux and shares the three Redux core principles, although with some key differences.

The official documentation provides the following definition:

WordPress’ data module serves as a hub to manage application state for both plugins and WordPress itself, providing tools to manage data within and between distinct modules. It is designed as a modular pattern for organizing and sharing data: simple enough to satisfy the needs of a small plugin, while scalable to serve the requirements of a complex single-page application.

By default, Gutenberg registers several data stores within the application state. Each of these stores has specific name and purpose:

Through these stores, you will be able to access a whole bunch of data:

  1. Data related to the current post, such as post title, excerpt, categories and tags, blocks, etc.
  2. Data related to the user interface, i.e. if a toggle is turned on or off.
  3. Data related to the entire WordPress installation, such as registered taxonomies, post types, blog title, authors, etc.

These stores live in the global wp object. To access the state of a store, you’ll use the select function.

To see how it works, create a new post or page and launch your browser’s inspector. Find the console and type in the following line of code:

wp.data.select("core")

The result will be an object including a list of functions you can use to get data from the core data store. These functions are called selectors and act as interfaces to access state values.

The Core WordPress data store object
The Core WordPress data store object

The WordPress data store includes information about WordPress in general and selectors are the way you’ll get that information. For example, getCurrentUser() returns details for the current user:

wp.data.select("core").getCurrentUser()
Inspecting getCurrentUser response
Inspecting getCurrentUser response

Another selector you can use to retrieve user details from the data store is getUsers():

wp.data.select("core").getUsers()

The following image shows the response object:

Inspecting getUsers response
Inspecting getUsers response

To get details for a single user, you can just type the following line:

wp.data.select("core").getUsers()[0]

Using the same selector you can also retrieve site users with author role assigned:

wp.data.select( 'core' ).getUsers({ who: 'authors' })

You can also retrieve registered taxonomies:

wp.data.select("core").getTaxonomies()
Inspecting getTaxonomies response.
Inspecting getTaxonomies response.

A list of the registered post types:

wp.data.select("core").getPostTypes()

Or a list of plugins:

wp.data.select("core").getPlugins()

Now let’s try to access a different data store. To do that, you’ll still use the select function, but providing a different namespace. Let’s try the following:

wp.data.select("core/edit-post")

Now you’ll get the following response object.

Accessing the Editor’s UI Data
Accessing the Editor’s UI Data

If you want to know whether the settings sidebar is open or not, you would use the isEditorSidebarOpened selector:

wp.data.select("core/edit-post").isEditorSidebarOpened()

This function returns true if the sidebar is open:

The sidebar is open.
The sidebar is open.

How to Access Post Data

You should now have a basic understanding of how to access data. Now we’ll take a closer look at a specific selector, the getEntityRecords function, which is the selector that gives access to the post data.

In the block editor, right click and select Inspect. In the Console tab, copy and paste the following line:

wp.data.select("core").getEntityRecords('postType', 'post')

This sends a request to the Rest API and returns an array of records corresponding to the last published blog posts.

getEntityRecords returns a list of posts.
getEntityRecords returns a list of posts.

getEntityRecords accepts three parameters:

  • kind string: Entity kind (i.e. postType).
  • name string: Entity name (i.e. post).
  • query ?Object: Optional terms query (i.e. {author: 0}).

You can build more specific requests using an object of arguments.

For example, you may decide that the response should only contain posts in a specified category:

wp.data.select("core").getEntityRecords('postType', 'post', {categories: 3})

You can also request only articles from a given author:

wp.data.select("core").getEntityRecords('postType', 'post', {author: 2})

If you click on any of the records returned by getEntityRecords, you get a list of properties for the selected record:

An example API request with getEntityRecords.
An example API request with getEntityRecords.

If you want the response to include the featured image, you’ll need to add an additional argument to your previous request:

wp.data.select("core").getEntityRecords('postType', 'post', {author: 2, _embed: true})
Featured image details in getEntityRecords response.
Featured image details in getEntityRecords response.

Now you should have a better understanding of how to access the WordPress datastore and retrieve post details. For a closer view at the getEntityRecords selector, see also Requesting data in Gutenberg with getEntityRecords.

How to Create a Dynamic Block: An Example Project

After our long theoretical premise, we can move on to practice and create a dynamic block using the tools we introduced in our previous block development tutorial.

In that article, we discussed:

  1. How to Set Up a WordPress Development Environment
  2. What is a Block Scaffolding
  3. How To Build a Static Gutenberg Block

That’s why we won’t be covering those topics in depth in the present article, but feel free to refer to our previous guide for any additional information, or just for a refresher.

Set Up A JavaScript Development Environment

Let’s start by setting up a JavaScript development environment.

Install or Update Node.js

First, install or update Node.js. Once you are done, launch your command line tool and run the following command:

node -v

You should see your node version.

Set Up Your Development Environment

Next, you’ll need a development environment for WordPress. For our examples, we used DevKinsta, our free WordPress development tool that enables you to launch a local WordPress website in no time.

Creating a custom site in DevKinsta
Creating a custom site in DevKinsta

But you are still free to choose any WordPress local development environment you like, such as MAMP or XAMPP, or even the official wp-env solution.

If you are using DevKinsta, click on New WordPress Site or on Custom Site, fill in the form fields and push Create site.

The installation process takes a minute or two. When it is complete, launch your local WordPress development website.

Site Info screen in DevKinsta.
Site Info screen in DevKinsta.

Set Up Your Block Plugin

What you need now is a starter block plugin. To avoid all the hassle of a manual configuration, the WordPress core developer team released the @wordpress/create-block tool, which is the official zero configuration tool for creating Gutenberg blocks.

We covered @wordpress/create-block in depth in our previous article, so here we can jump start the set-up right away.

In your command line tool, navigate to the /wp-content/plugins folder:

New terminal at folder in Mac OS.
New terminal at folder in Mac OS.

Once there, run the following command:

npx @wordpress/create-block

You are now ready to install the @wordpress/create-block package:

Installing the @wordpress/create-block package.
Installing the @wordpress/create-block package.

To confirm, type y and press Enter.

This generates the plugin’s PHP, SCSS, and JS files in interactive mode.

Below are the details we will be using in our example. Feel free to change these details according to your preferences:

Once you hit enter, it downloads and configures the plugin.

Installing the block plugin.
Installing the block plugin.

The process may take a couple of minutes. When it is complete, you should see the following screen:

Block bootstrapped in the plugin folder.
Block bootstrapped in the plugin folder.

You will see a list of the commands you can run from within the plugin directory:

  • $ npm start – Start the build for development.
  • $ npm run build – Build the code for production.
  • $ npm run format – Format files.
  • $ npm run lint:css – Lint CSS files.
  • $ npm run lint:js – Lint JavaScript files.
  • $ npm run packages-update – Update WordPress packages to the latest version.

Okay, now move to the plugin directory with the following command:

cd author-plugin

And start your development build:

npm start

Next, navigate to the Plugins screen in your WordPress dashboard and activate the Author box plugin:

The block plugin is listed in the Plugins screen.
The block plugin is listed in the Plugins screen.

Now you can check if the plugin is working correctly. Create a new post and start typing / to launch the quick inserter:

The block item in the Quick Inserter.
The block item in the Quick Inserter.

You’ll also find the Author box block in the Block Inserter, under the Widgets category. Select the block to add it to the editor canvas:

The WordPress Block Inserter.
The WordPress Block Inserter

You’re done. Now save the post and preview the page to check if the block displays correctly.

The Block Scaffolding

We covered the block scaffolding in our previous post. So, here we will only provide a quick overview of the files we are going to modify for our examples.

The Root Folder
The root folder is where you’ll find the main PHP file and several subfolders.

author-plugin.php
By default, the @wordpress/create-block package provides the following PHP file:

/**
 * Plugin Name:       Author box
 * Description:       An example block for Kinsta readers
 * Requires at least: 5.8
 * Requires PHP:      7.0
 * Version:           0.1.0
 * Author:            Carlo
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       author-plugin
 *
 * @package           author-box
 */

/**
 * Registers the block using the metadata loaded from the `block.json` file.
 * Behind the scenes, it registers also all assets so they can be enqueued
 * through the block editor in the corresponding context.
 *
 * @see https://developer.wordpress.org/reference/functions/register_block_type/
 */
function author_box_author_plugin_block_init() {
	register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'author_box_author_plugin_block_init' );

In the heading, you’ll notice the details we entered on setup.

With static blocks, most of the time you’ll be working on the JavaScript files located in the src folder. With dynamic blocks, you’ll write PHP code to display the block content on the front end.

The src Folder
The src folder is your development folder. Here you’ll find the following files:

  • block.json
  • index.js
  • edit.js
  • save.js
  • editor.scss
  • style.scss

block.json
The block.json is your metadata file. @wordpress/create-block generates the following block.json file:

{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 2,
	"name": "author-box/author-plugin",
	"version": "0.1.0",
	"title": "Author box",
	"category": "widgets",
	"icon": "businessperson",
	"description": "An example block for Kinsta readers",
	"supports": {
		"html": false
	},
	"textdomain": "author-plugin",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css"
}

For a closer view at the block.json file in general, please refer to our previous blog post.

index.js
The index.js file is where you register the block type on the client:

import { registerBlockType } from '@wordpress/blocks';

import './style.scss';

import Edit from './edit';
import save from './save';

registerBlockType('author-box/author-plugin', {
	edit: Edit,
	save,
});

edit.js
The edit.js file is where you’ll build the block interface rendered in the editor:

import { __ } from '@wordpress/i18n';

import { useBlockProps } from '@wordpress/block-editor';

import './editor.scss';

export default function Edit() {
	return (
		<p {...useBlockProps()}>
			{__('Author box – hello from the editor!', 'author-plugin')}
		</p>
	);
}

save.js
The save.js file contains the script that builds the block content to be saved into the database. We won’t use this file in this tutorial:

import { __ } from '@wordpress/i18n';

import { useBlockProps } from '@wordpress/block-editor';

export default function save() {
	return (
		<p {...useBlockProps.save()}>
			{__('Author box – hello from the saved content!', 'author-plugin')}
		</p>
	);
}

Building the Block to Render in the Editor

Open your project in Visual Studio Code or any code editor you like.

If you are using Visual Studio Code, go to Terminal -> New Terminal. This will launch a terminal window on your project’s root folder.

In the terminal (or in your favorite command line tool), type in the following command:

npm start

You’re now running the node environment in development mode.

The block plugin project in Visual Studio Code.
The block plugin project in Visual Studio Code.

From here on, you’ll be following two different routes. To render the block in the editor, you’ll work in the edit.js file. To render the block on the front-end, you’ll need to write PHP code in the main plugin file.

Now roll up your sleeves because the coding begins:

Register the Block on the Server

First, you have to register the block on the server and write the PHP code to retrieve data from the database.

In the author-plugin.php file, you will need to pass a second argument to the register_block_type function:

function author_box_author_plugin_block_init() {
	register_block_type( __DIR__ . '/build', array(
		'render_callback' => 'author_box_author_plugin_render_author_content'
	) );
}
add_action( 'init', 'author_box_author_plugin_block_init' );

The second argument is an array of arguments for registering a block type (see the full list of available arguments here). In the code above we have only provided render_callback, which determines the callback function that renders the block on the screen.

Next, you will declare the function:

function author_box_author_plugin_render_author_content() {
	return 'Hello World!';
}

Save the file, create a new post or page, and add the Author Box block to the editor canvas.

The WordPress Block Inserter.
The WordPress Block Inserter.

The block editor is still showing the starter block, as we haven’t changed the edit.js file yet.

But if you preview the post in the front-end, you’ll see that the original block content has now been replaced by the “Hello World” string.

Now, since the HTML rendered on the front-end is generated by the PHP file, there will be no need for the save function to return anything. So let’s go straight to the save.js file and change the code as shown below:

export default function save() {
	return null;
}

Define Block Attributes

Now you need a place to store user settings. For example, the number of post items to retrieve from the database, whether to display or not a specified field, etc. To do that, you’ll define a number of attributes in the block.json file.

For example, you could give the user the ability to determine the number of posts to be included in the block, the option to display featured image, date, excerpt, and/or hide/show the author’s profile picture.

Here is the full list of attributes we will use to build our example block:

{
	...
	"attributes": {
		"numberOfItems": {
			"type": "number",
			"default": 3
		},
		"columns": {
			"type": "number",
			"default": 1
		},
		"displayDate": {
			"type": "boolean",
			"default": true
		},
		"displayExcerpt": {
			"type": "boolean",
			"default": true
		},
		"displayThumbnail": {
			"type": "boolean",
			"default": true
		},
		"displayAuthorInfo": {
			"type": "boolean",
			"default": true
		},
		"showAvatar": {
			"type": "boolean",
			"default": true
		}, 
		"avatarSize": {
			"type": "number",
			"default": 48
		},
		"showBio": {
			"type": "boolean",
			"default": true
		}
	}
}

Build the Block to Be Rendered in the Editor

The getEntityRecords selector is included in the @wordpress/data package. To use it, you’ll need to import the useSelect hook from that package in your edit.js file:

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

Next, add the following code to the Edit() function:

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

In the code above, we hardcoded the number of posts. But you may want to give users the ability to set a different number of posts. You can use an attribute for that.

In your block.json you should have defined a numberOfItems attribute. You can use it in your Edit function as shown below:

export default function Edit( { attributes } ) {

	const { numberOfItems } = attributes;

	const posts = useSelect( ( select ) => {
		return select( 'core' ).getEntityRecords( 'postType', 'post', {
			'per_page': numberOfItems
		});
	});

	console.log( posts );

	return (
		...
	);
}

You won’t see the posts on the screen yet, but run a console.log and see what happens in your browser inspector’s console:

The result in the browser's console.
The result in the browser’s console.

useSelect may take two arguments: an inline callback and an array of dependencies. Both return a memoized version of the callback that only changes when one of the dependencies changes.

So, in order to refetch posts on every numberOfItems attribute change, you have to change the Edit function as shown below:

export default function Edit( { attributes } ) {

	const { numberOfItems } = attributes;

	const posts = useSelect(
		( select ) => {
			return select( 'core' ).getEntityRecords( 'postType', 'post', {
				'per_page': numberOfItems
			});
		}, 
		[ numberOfItems ]
	);

	console.log(posts);

	return (
		...
	);
}

Next you have to render your list of posts. To do that you could use the built-in JavaScript map method:

export default function Edit( { attributes } ) {

	const { numberOfItems } = attributes;

	const posts = useSelect(
		( select ) => {
			return select( 'core' ).getEntityRecords( 'postType', 'post', {
				'per_page': numberOfItems
			});
		},
		[ numberOfItems ]
	);

	console.log(posts);
	
	return (
		<div { ...useBlockProps() }>
			<ul>
				{ posts && posts.map( ( post ) => {
					return (
						<li key={ post.id }>
							<h5>
								<a href={ post.link }>
									{ 
										post.title.rendered ? 
										post.title.rendered :
										__( 'Default title', 'author-plugin' )
									}
								</a>
							</h5>
						</li>
					)
				})}
			</ul>
		</div>
	);
}

First, it checks if you have at least one post in the array, then runs the loop.

Note that, as we’re using the map method with a React component, we are also using a key attribute to assign the post ID to the current list item.

post.link and post.title.rendered render the post URL and title respectively.

The image below shows the full list of the post object properties.

The Post object.
The Post object.

The code above is just a basic example of usage of getEntityRecords. Now it’s time to put our knowledge into practice.

Say you want to prevent your block from rendering HTML tags that the user may have added to the post title. WordPress provides a RawHTML component for that.

First, you’ll import the component from the @wordpress/element package:

import { RawHTML } from '@wordpress/element';

Next, you’ll wrap the post title within a RawHTML element:

<div { ...useBlockProps() }>
	<ul>
		{ posts && posts.map((post) => {
			return (
				<li key={ post.id }>
					<h5>
						<a href={ post.link }>
							{ post.title.rendered ? (
								<RawHTML>
									{ post.title.rendered }
								</RawHTML>
							) : (
								__( 'Default title', 'author-plugin' )
							)}
						</a>
					</h5>
				</li>
			)
		})}
	</ul>
</div>

And that’s it. Now add an HTML tag to your post title and save the post. Then test your code with and without RawHTML and see how your block’s content changes on the screen.

Add the Date

WordPress provides a number of JavaScript functions to manage and format dates. To use those functions you’ll first need to import them from the @wordpress/date package in your edit.js file:

import { dateI18n, format, __experimentalGetSettings } from '@wordpress/date';
  • dateI18n: Format a date, translating it into site’s locale.
  • format: Format a date.
  • __experimentalGetSettings: Display the date in the format set in WordPress general settings.

Those functions are not documented, but you’ll find useful examples in the source code of several blocks. See for instance the latest-posts and post-date edit.js files.

Now add the displayDate attribute:

const { numberOfItems, displayDate } = attributes;

Then add the following code within the <li> element:

{ 
	displayDate && (
		<time
			className='wp-block-author-box-author-plugin__post-date'
			dateTime={ format( 'c', post.date_gmt ) }
		>
			{ dateI18n(
				__experimentalGetSettings().formats.date, 
				post.date_gmt
			)}
		</time>
	) 
}

What happens here?

  • If displayDate is true, then display the date using a time element.
  • The dateTime attribute provides the time and/or date of the element in one of the allowed formats.
  • dateI18n retrieves the date in localized format. This function works in a way similar to the PHPPHP date_i18n WordPress function.

Add the Excerpt

Now it should be easy to add the post excerpt. First, take a look at the excerpt property in the browser’s inspector. You’ll see that the actual content is stored in excerpt.rendered.

Inspecting the post excerpt in Chrome DevTools.
Inspecting the post excerpt in Chrome DevTools.

Next, add the displayExcerpt attribute to the attributes object:

const { numberOfItems, displayDate, displayExcerpt } = attributes;

Then add the following code before the </li> closing tag in the Edit function:

{
	displayExcerpt &&
	post.excerpt.rendered && (
		<p>
			<RawHTML>
				{ post.excerpt.rendered }
			</RawHTML>
		</p>
	)
}

If you are not familiar with JavaScript, here and above we used the Short Circuit Evaluation, whereby, if all conditions are true, then the value of the last operand is returned (read more in Inline If with Logical && Operator and Logical AND (&&)).

Finally, you can test your code again. Change the attribute value in the block.json file and see what happens in the editor.

Add the Featured Image

Now you need to add the code that renders the featured images. Start adding the displayThumbnail attribute to attributes:

Struggling with downtime and WordPress problems? Kinsta is the hosting solution designed to save you time! Check out our features
const { 
	numberOfItems, 
	displayDate, 
	displayExcerpt, 
	displayThumbnail 
} = attributes;

Now you need to figure out where the featured image is stored. As we mentioned above, to get the featured image you need to add a new _embed argument to your query. Back to your code, change the query arguments as follows:

const posts = useSelect(
	( select ) => {
		return select( 'core' ).getEntityRecords( 'postType', 'post', {
			'per_page': numberOfItems,
			'_embed': true
		});
	},
	[ numberOfItems ]
);

Here we simply added '_embed': true to the array of arguments. This provides a post object containing the _embedded property, which provides the image details you need to dispay the featured images.

Now you should know where to find the image details.

Featured image details in getEntityRecords response.
Featured image details in getEntityRecords response.

You just need to add the code that renders the image on the screen:

{
	displayThumbnail && 
	post._embedded && 
	post._embedded['wp:featuredmedia'] &&
	post._embedded['wp:featuredmedia'][0] &&
	<img 
	className='wp-block-author-box-author-plugin__post-thumbnail'
		src={ post._embedded['wp:featuredmedia'][0].media_details.sizes.medium.source_url }
		alt={ post._embedded['wp:featuredmedia'][0].alt_text }
	/>
}

Save the file, switch to the block editor, and check if the image displays correctly when the displayThumbnail attribute is set to true.

A list of posts with featured image, date and excerpt.
A list of posts with featured image, date and excerpt.

Add Sidebar Controls

So far we have been using the attribute default values set in the block.json. But from our previous article we know that we can define event handlers to give users the ability to assign custom values to each attribute.

To do that, you’ll add a set of controls to the block settings sidebar. In edit.js, import the following components from the corresponding packages:

import { 
	useBlockProps,
	InspectorControls
} from '@wordpress/block-editor';

import {
	PanelBody,
	PanelRow,
	QueryControls,
	ToggleControl,
	RangeControl
} from '@wordpress/components';
  • InspectorControls: Contains sidebar settings that affect the entire block (see on GitHub)
  • PanelBody: Adds a collapsible container to the Settings Sidebar (see on GitHub)
  • PanelRow: Produces a generic container for sidebar controls (see on GitHub)
  • QueryControls: Provides settings controls to build a query (see on GitHub)
  • ToggleControl: Provides a toggle button for users to enable/disable a specific option (see on GitHub)
  • RangeControl: Is used to make selections from a range of incremental values (see on GitHub)

Next, you need to update the Edit function to use the controls now available. First, modify the Edit function as follows:

export default function Edit( { attributes, setAttributes } ) {

	const { 
		numberOfItems, 
		columns, 
		displayExcerpt, 
		displayDate, 
		displayThumbnail
	} = attributes;

	const posts = useSelect(
		( select ) => {
			return select( 'core' ).getEntityRecords( 'postType', 'post', {
				'per_page': numberOfItems,
				'_embed': true
			});
		},
		[ numberOfItems ]
	);
	...
}

Note the setAttributes property passed to the Edit function.

Now you can add the corresponding elements to your JSX code:

return (
	<>
		<InspectorControls>
			<PanelBody title={ __( 'Content Settings', 'author-plugin' ) }>
				<PanelRow>
					<QueryControls 
						numberOfItems={ numberOfItems }
						onNumberOfItemsChange={ ( value ) =>
							setAttributes( { numberOfItems: value } )
						}
						minItems={ 1 }
						maxItems={ 10 }
					/>
				</PanelRow>
				<PanelRow>
					<RangeControl
						label={ __( 'Number of Columns', 'author-plugin' ) }
						value={ columns }
						onChange={ ( value ) =>
							setAttributes( { columns: value } )
						}
						min={ 1 }
						max={ 4 }
						required
					/>
				</PanelRow>
				<PanelRow>
					<ToggleControl
						label={ __( 'Show Featured Image', 'author-plugin' ) }
						checked={ displayThumbnail }
						onChange={ () =>
							setAttributes( { displayThumbnail: ! displayThumbnail } )
						}
					/>
				</PanelRow>
				<PanelRow>
					<ToggleControl
						label={ __( 'Show Date', 'author-plugin' ) }
						checked={ displayDate }
						onChange={ () =>
							setAttributes( { displayDate: ! displayDate } )
						}
					/>
				</PanelRow>
				<PanelRow>
					<ToggleControl
						label={ __( 'Display Excerpt', 'author-plugin' ) }
						checked={ displayExcerpt }
						onChange={ () =>
							setAttributes( { displayExcerpt: ! displayExcerpt } )
						}
					/>
				</PanelRow>
			</PanelBody>
		</InspectorControls>
		<div { ...useBlockProps() }>
			...
		</div>
	</>
);

Wow, that’s a lot of code, isn’t it? But it’s pretty easy to understand.

The element attributes that are the most worthy of your attention here are onNumberOfItemsChange in QueryControls and onChange in RangeControl and ToggleControl. Those attributes set the event handlers needed to enable the user to customize the appearance and/or behavior of a block.

You will also notice that we used <> and </> tags, which are the short syntax for declaring React fragments.

Now, save your file, hop over into the editor, and refresh the page:

Block settings.
Block settings.

Is everything in there? Then let’s move on and add the post author’s details.

Find the Post Author

As we mentioned above, our block will show a list of articles written by the same author as the current post.

To get the post author’s ID, you’ll import the getCurrentPostAttribute selector from the core/editor datastore:

wp.data.select( 'core/editor' ).getCurrentPostAttribute( 'author' )

getCurrentPostAttribute returns an attribute value for the saved post.

Once you get the author ID, you can change the query as shown below:

const posts = useSelect(
	( select ) => {

		const _authorId = select( 'core/editor' ).getCurrentPostAttribute( 'author' );
	
		return select( 'core' ).getEntityRecords( 'postType', 'post', {
			'author': _authorId,
			'per_page': numberOfItems,
			'_embed': true
		});
	},
	[ numberOfItems ]
);

With this code, you’ll get a list of n articles by the same author as the current post.

Now that you have the author ID, you can also use it to fetch additional data from the database.

Display Author Details

Since we don’t have any documentation available, we used the code from the core Post Author block as a reference.

To display author details, you first need to import a new dependency:

import { forEach } from 'lodash';

Then, in the Edit function, update the attributes object as follows:

const { 
	numberOfItems, 
	columns, 
	displayExcerpt, 
	displayDate, 
	displayThumbnail, 
	displayAuthorInfo, 
	showAvatar, 
	avatarSize, 
	showBio 
} = attributes;

Once done, you’ll edit the code seen in the previous section to retrieve author details:

const { authorDetails, posts } = useSelect(
	( select ) => {

		const _authorId = select( 'core/editor' ).getCurrentPostAttribute( 'author' );

		const authorDetails = _authorId ? select( 'core' ).getUser( _authorId ) : null;
	
		const posts = select( 'core' ).getEntityRecords( 'postType', 'post', {
			'author': _authorId,
			'per_page': numberOfItems,
			'_embed': true
		});

		return { 
			authorDetails: authorDetails,
			posts: posts
		};
	},
	[ numberOfItems ]
);

Note that we used the getUser selector to get the author details.

Next, you may want to get the author’s avatar. The code below builds an array of items storing avatar URLs and sizes:

const avatarSizes = [];
if ( authorDetails ) {
	forEach( authorDetails.avatar_urls, ( url, size ) => {
		avatarSizes.push( {
			value: size,
			label: `${ size } x ${ size }`,
		} );
	} );
}

Then you’ll add the sidebar panels and controls to enable users to customize the author’s area in the block:

return (
	<>
		<InspectorControls>
			<PanelBody title={ __( 'Author Info', 'author-plugin' ) }>
				<PanelRow>
					<ToggleControl
						label={ __( 'Display Author Info', 'author-plugin' ) }
						checked={ displayAuthorInfo }
						onChange={ () =>
							setAttributes( { displayAuthorInfo: ! displayAuthorInfo } )
						}
					/>
				</PanelRow>
				{ displayAuthorInfo && (
					<>
						<PanelRow>
							<ToggleControl
								label={ __( 'Show avatar' ) }
								checked={ showAvatar }
								onChange={ () =>
									setAttributes( { showAvatar: ! showAvatar } )
								}
							/>
							{ showAvatar && (
								<SelectControl
									label={ __( 'Avatar size' ) }
									value={ avatarSize }
									options={ avatarSizes }
									onChange={ ( size ) => {
										setAttributes( {
											avatarSize: Number( size ),
										} );
									} }
								/>
							) }
						</PanelRow>
						<PanelRow>
							<ToggleControl
								label={ __( 'Show Bio', 'author-plugin' ) }
								checked={ showBio }
								onChange={ () =>
									setAttributes( { showBio: ! showBio } )
								}
							/>
						</PanelRow>
					</>
				) }
			</PanelBody>
			...
		</InspectorControls>
		...
	</>
);

The image below shows the updated settings sidebar:

The Author Info settings panel.
The Author Info settings panel.

Finally, you can add the author’s section to your block:

return (
	<>
		<InspectorControls>
		...
		</InspectorControls>

		<div { ...useBlockProps() }>
			{ displayAuthorInfo  && authorDetails && (
				<div className="wp-block-author-box-author-plugin__author">
					{ showAvatar && (
						<div className="wp-block-author-box-author-plugin__avatar">
							<img
								width={ avatarSize }
								src={
									authorDetails.avatar_urls[
										avatarSize
									]
								}
								alt={ authorDetails.name }
							/>
						</div>
					) }
					<div className='wp-block-author-box-author-plugin__author-content'>
						<p className='wp-block-author-box-author-plugin__name'>
							{ authorDetails.name }
						</p>
						{ showBio &&
							// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
							authorDetails?.description &&
							authorDetails.description.length > 0 && (
							<p className='wp-block-author-box-author-plugin__description'>{ authorDetails.description }</p>
						) }
					</div>
				</div>
			)}
			<ul>
			...
			</ul>
		</div>
	</>
);

The following image shows how it renders on the screen.

Author details section and Info settings.
Author details section and Info settings.

Now save your edit.js file and run your tests. Your block should include different elements depending on block settings.

Author details not showing author's bio.
Author details not showing author’s bio.

One last thing is still missing: the number of columns to display articles.

Change the Number of Columns

To give the user the ability to show article previews in columns, we defined the columns attribute in the block.json file. We also included a columns attribute in the script and created a settings control to allow users to change the number of columns, although this change has no effect at the moment.

In the JSX code above you should have noticed that we added CSS classes to several elements:

Classes assigned to elements in the Author section:

  • wp-block-author-box-author-plugin__author
  • wp-block-author-box-author-plugin__avatar
  • wp-block-author-box-author-plugin__author-content
  • wp-block-author-box-author-plugin__name
  • wp-block-author-box-author-plugin__description

Classes assigned to elements in the content section:

  • wp-block-author-box-author-plugin__post-items
  • wp-block-author-box-author-plugin__post-thumbnail
  • wp-block-author-box-author-plugin__post-title
  • wp-block-author-box-author-plugin__post-date
  • wp-block-author-box-author-plugin__post-excerpt

One class is still missing. The name of this class will be generated dynamically to reflect the number of columns set by the user.

Go back to the Edit.js file and modify the ul element as follows:

<ul className={ `wp-block-author-box-author-plugin__post-items columns-${ columns }` }>
	...
</ul>

We added a new columns-${ columns } class according to the Template literals syntax to insert an expression inside a string. This way, the attribute attached to the ul element will depend on user settings (e.g. columns-1, columns-2, etc.).

Now open the style.scss file and replace the existing code with the following:

.wp-block-author-box-author-plugin {
	background-color: #21759b;
	color: #fff;
	padding: .6em;
	ul.wp-block-author-box-author-plugin__post-items {
		padding: 0;
		list-style-type: none;
		display: grid;
		gap: .5em;
		@for $i from 2 through 4 {
			&.columns-#{ $i } {
				grid-template-columns: repeat(#{ $i }, 1fr);
			}
		}
		li {
			list-style: none;
			img.wp-block-author-box-author-plugin__post-thumbnail {
				height: auto;
				max-width: 100%;
			}
		}
		
	}
}
.wp-block-author-box-author-plugin__author {
	display: flex;
    flex-wrap: wrap;
}

.wp-block-author-box-author-plugin__avatar {
	margin-right: 1em;
}

.wp-block-author-box-author-plugin__author-content {
	flex-basis: 0;
    flex-grow: 1;
}

We won’t go deep in that code, being beyond the scope of this article. But if you wish to dive deeper, you could refer to the following resources:

The Author block in the editor.
The Author block in the editor.

And that’s it for the rendering of the block in the editor.

Building the Block to Render on the Page

Now that the code that renders the block in the editor is complete, we can move on and build the block for rendering on the front end.

As we mentioned earlier, when it comes to dynamic blocks, the plugin file is responsible to generate the HTML to be rendered on the front end.

So, open the main file of your plugin (author-plugin.php in our example).

The first thing to do is to make the block attributes available to the WordPress PHP function. In your PHP file, change the function definition as follows:

function author_box_author_plugin_render_author_content( $attr ) {
	...
}

Now you can use the WordPress functions to retrieve and manipulate data. For example, you can use get_posts to retrieve the latest blog posts (read more in our in-depth article covering the get_posts function):

function author_box_author_plugin_render_author_content( $attr ) {
	$args = array(
		'numberposts'	=> $attr['numberOfItems'],
	);
	$my_posts = get_posts( $args );
	
	if( ! empty( $my_posts ) ){
		$output = '<ul>';
		foreach ( $my_posts as $p ){
			$output .= '<li><a href="' . esc_url( get_permalink( $p->ID ) ) . '">' 
			. $p->post_title . '</a></li>';
		}
		$output .= '</ul>';
	}
	return $output ?? '<strong>Sorry. No posts matching your criteria!</strong>';
}

The function above retrieves the latest numberOfItems blog posts from your WordPress database (by default post_type is set to post) and returns an array of $post objects. Than it iterates over the array to build the list items.

If you inspect the HTML output, you’ll note that it’s a simple list of posts, like the one shown in the following image:

A simple list of posts.
A simple list of posts.

In our previous article we mentioned that you’ll use the useBlockProps React hook to mark the block’s wrapper element in your JSX code. You’ll need to do the same in your PHP function.

WordPress provides the get_block_wrapper_attributes function for that.

So, change your PHP code as follows:

function author_box_author_plugin_render_author_content( $attr ) {
	$args = array(
		'numberposts'	=> $attr['numberOfItems']
	);
	$my_posts = get_posts( $args );
	
	if( ! empty( $my_posts ) ){
		$output = '<div ' . get_block_wrapper_attributes() . '>';
		$output .= '<ul>';
		foreach ( $my_posts as $p ){
			
			$title = $p->post_title ? $p->post_title : 'Default title';
			$url = esc_url( get_permalink( $p->ID ) );

			$output .= '<li>';
			$output .= '<a href="' . $url . '">' . $title . '</a>';
			$output .= '</li>';
		}
		$output .= '</ul>';
		$output .= '</div>';
	}
	return $output ?? '<strong>Sorry. No posts matching your criteria!</strong>';
}

Now a wp-block-author-box-author-plugin class has been assigned to the container element and the block has a different background color.

Then the get_posts function gets WP_Posts data and the foreach cycle builds the list items (see also How to Display get_posts Returned Data).

A list of posts with a CSS class assigned.
A list of posts with a CSS class assigned.

Add Featured Image, Date, and Excerpt

Next, you’ll need to add post thumbnails, dates and excerpts. In the same file, change your PHP code as following:

function author_box_author_plugin_render_author_content( $attr ) {
	$args = array(
		'numberposts'	=> $attr['numberOfItems']
	);
	$my_posts = get_posts( $args );
	
	if( ! empty( $my_posts ) ){
		$output = '<div ' . get_block_wrapper_attributes() . '>';
		$output .= '<ul class="wp-block-author-box-author-plugin__post-items columns-">';

		foreach ( $my_posts as $p ){
			
			$title = $p->post_title ? $p->post_title : 'Default title';
			$url = esc_url( get_permalink( $p->ID ) );
			$thumbnail = has_post_thumbnail( $p->ID ) ? get_the_post_thumbnail( $p->ID, 'medium' ) : '';

			$output .= '<li>';
			if( ! empty( $thumbnail ) && $attr['displayThumbnail'] ){
				$output .= $thumbnail;
			}
			$output .= '<h5><a href="' . $url . '">' . $title . '</a></h5>';
			if( $attr['displayDate'] ){
				$output .= '<time datetime="' . esc_attr( get_the_date( 'c', $p ) ) . '">' . esc_html( get_the_date( '', $p ) ) . '</time>';
			}
			if( get_the_excerpt( $p ) && $attr['displayExcerpt'] ){
				$output .= '<p>' . get_the_excerpt( $p ) . '</p>';
			}
			$output .= '</li>';
		}
		$output .= '</ul>';
		$output .= '</div>';
	}
	return $output ?? '<strong>Sorry. No posts matching your criteria!</strong>';
}

The foreach loop iterates over the $my_posts array. At each iteration, several conditions check attribute values and build the output accordingly.

Now take a look at the output on the screen:

A list of posts with featured images, dates, and excerpts.
A list of posts with featured images, dates, and excerpts.

Now you can run your tests. Change date, excerpt, and thumbnail settings and check how the block content changes on the front-end.

Display Posts in Columns

In our JavaScript code, we used a columns-${ columns } class to display post previews in columns. Now we need to do the same in PHP.

To do that, you simply have to add these two lines of code:

$num_cols = $attr['columns'] > 1 ? strval( $attr['columns'] ) : '1';

$output .= '<ul class="wp-block-author-box-author-plugin__post-items columns-' . $num_cols . '">';

This will append a columns-n class to the ul element containing the post previews. Now the number of columns displayed on the page should match the number of columns set in the block settings.

Build the Author Box

Last, you need to build the box containing the author’s details, including avatar, name, and description.

Within the callback function, you’ll need to add a set of conditions to check the current value of each attribute:

if( $attr['displayAuthorInfo'] ){
	$output .= '<div class="wp-block-author-box-author-plugin__author">';
	
	if( $attr['showAvatar'] ){
		$output .= '<div class="wp-block-author-box-author-plugin__avatar">' 
			. get_avatar( get_the_author_meta( 'ID' ), $attr['avatarSize'] ) 
			. '</div>';
	}

	$output .= '<div class="wp-block-author-box-author-plugin__author-content">';
	
	$output .= '<div class="wp-block-author-box-author-plugin__name">' 
		. get_the_author_meta( 'display_name' ) 
		. '</div>';

	if( $attr['showBio'] ){
		$output .= '<div class="wp-block-author-box-author-plugin__description">' 
			. get_the_author_meta( 'description' ) 
			. '</div>';
	}

	$output .= '</div>';
	$output .= '</div>';
}

The code is quite straightforward. It checks the current value of each attribute, and if it is true, then it generates the necessary HTML.

Now save your PHP file and compare the block in the editor vs the same block on the front end.

Our custom block in the block editor.
Our custom block in the block editor.

You’ll find the full code of the example block in this public Gist.

Dynamic blocks are Gutenberg blocks under steroids ? and this guide covers all you need to know to create dynamic blocks for your next project ?Click to Tweet

Recommended Resources for Dynamic Block Development

If you perked up your ears while reading this article and started recognizing the professional development opportunities coming with learning how to create Gutenberg blocks, well, our advice is to continue exploring and acquiring new skills in the technologies behind block development.

Although reliable official documentation is still missing, nonetheless there are excellent resources out there, both free and paid, we consulted while writing this article. Among the many resources available, we recommend the following:

Official Resources

Recommended Tutorials from WordPress Core Contributors

JavaScript, React, and Redux Resources

Related Resources from Kinsta

Summary

We have reached the end of this (second) long journey through Gutenberg block development.

In this article, we covered some advanced topics, such as Application State and Redux stores. But hopefully, you should now have a better understanding of block development in general.

Yes, Node.js, Webpack, Babel, React, and Redux skills are essential when it comes to building advanced Gutenberg blocks, but you don’t need to be a React ninja to get started. Learning how to develop Gutenberg blocks doesn’t necessarily have to be complicated. Just do it with the right motivation and by following the appropriate learning path.

And we hope this article – and the previous one – provide you with the right map to find your path and get started with Gutenberg development right away.

Up to you now! Have you created dynamic blocks yet? Do you have any examples to share with us? And what were the biggest hurdles in your experience? Feel free to drop a comment below.

The post How to Create Dynamic Blocks for Gutenberg appeared first on Kinsta®.

版权声明:
作者:lichengxin
链接:https://www.techfm.club/p/33729.html
来源:TechFM
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>