Roots Discourse

How to display ACF fields in my custom Gutenberg block

Hi there,

I know this is not a Sage specific question, but more an overall Wordpress Gutenberg best practice question :slight_smile:

We’re slowly migrating our wordpress theme development process over to the Gutenberg editor and I made quit a few custom Gutenberg blocks by now, either with the Wordpress Block API or with the ACF Blocks. I try to use the wp core blocks as much as possible for the best user experience when editing.

On a custom post type project I want to have a leading paragraph, followed by a normal paragraph. Next to this I want to list some meta data as a static list, coming from a few ACF fields on the post type. This data is used elsewhere in my theme so I want to keep these as ACF fields.

I also would like this static list to be visible in the Gutenberg editor, so it matches the actual frontend.
So I’m creating a custom Gutenberg block for this using:

function registerBlocks() {
	wp_register_script(
		'domain/opening-paragraph-editor',
		asset('scripts/opening-paragraph.editor.js')->uri(),
		['sage/vendor.js']
	);

	wp_register_style(
		'domain/opening-paragraph',
		asset('styles/opening-paragraph.css')->uri()
	);

	register_block_type(
		'domain/opening-paragraph', [
			'api_version'     => 2,
			'editor_script'   => 'domain/opening-paragraph-editor',
			'editor_style'    => 'domain/opening-paragraph',
			'render_callback' => function($block_attributes, $content) {
				wp_enqueue_style('domain/opening-paragraph');
				return $content;
			}
		]
	);
}
add_action('init', 'registerBlocks');

My scripts/opening-paragraph.editor.js:

import React  from 'react';
import {__} from '@wordpress/i18n';
import {registerBlockType} from '@wordpress/blocks';
import {RichText, InnerBlocks, useBlockProps} from '@wordpress/block-editor';

const ALLOWED_BLOCKS = ['core/paragraph'];
const TEMPLATE = [
	['core/paragraph', {
		className: 'lead'
	}],
	['core/paragraph']
];

registerBlockType('domain/lead-project', {
	apiVersion: 2,
	title: __('Lead Project'),
	description: __('Opening lead paragraph with project meta block.'),
	icon: 'editor-paragraph',
	category: 'text',
	attributes: {
		projectClient: {
			type: 'string'
		},
		projectDate: {
			type: 'string'
		},
		projectLocation: {
			type: 'string'
		}
	},
	example: {
		attributes: {
			projectClient: 'Awakenings',
			projectDate: '23 MAR 2014',
			projectLocation: 'Gashouder, Amsterdam'
		}
	},
	edit: ({attributes, setAttributes}) => {
		const blockProps = useBlockProps();
		const {projectClient, projectDate, projectLocation} = attributes;

		return (
			<div { ...blockProps }>
				<div className="wp-block-domain-lead-project__wrap">
					<div className="wp-block-domain-lead-project__inner">
						<InnerBlocks
							allowedBlocks={ALLOWED_BLOCKS}
							template={TEMPLATE}
							templateLock="all"
						/>
					</div>
					<div className="wp-block-domain-lead-project__summary">
						<RichText
							tagName="p"
							value={projectClient}
							contentEditable={false}
							onChange={text => setAttributes({projectClient: text})}
						/>
						<RichText
							tagName="p"
							value={projectDate}
							contentEditable={false}
							onChange={text => setAttributes({projectDate: text})}
						/>
						<RichText
							tagName="p"
							value={projectLocation}
							contentEditable={false}
							onChange={text => setAttributes({projectLocation: text})}
						/>
					</div>
				</div>
			</div>
		);
	},
	save: ({attributes}) => {
		const blockProps = useBlockProps.save();
		const {projectClient, projectDate, projectLocation} = attributes;

		return (
			<div { ...blockProps }>
				<div className="wp-block-domain-lead-project__wrap">
					<div className="wp-block-domain-lead-project__inner">
						<InnerBlocks.Content/>
					</div>
					<RichText.Content
						tagName="p"
						value={projectClient}
					/>
					<RichText.Content
						tagName="p"
						value={projectDate}
					/>
					<RichText.Content
						tagName="p"
						value={projectLocation}
					/>
				</div>
			</div>
		);
	}
});

And for locking this block to the post type and passing the data as attributes, I’m creating a Block template:

function registerBlockTemplates() {
	$post_id = isset($_GET['post']) ? $_GET['post'] : null;

	if (get_post_type($post_id) == 'project') {
		$project_object   = get_post_type_object('project');
		$projectClient    = get_field('project_client', $post_id);
		$projectDateStart = get_field('project_dates_start', $post_id);
		$projectDateEnd   = get_field('project_dates_end', $post_id);
		$projectLocation  = get_field('project_location', $post_id);

		$project_object->template = [
			['domain/lead-project', [
				'projectClient'   => $projectClient ?: '',
				'projectDate'     => $projectDateStart ? $projectDateStart . ($projectDateEnd ? ' – ' . $projectDateEnd : '') : '',
				'projectLocation' => $projectLocation
			]],
			['domain/inner-blocks']
		];
		$project_object->template_lock = 'all';
	}
}
add_action('init', 'registerBlockTemplates', 999, 0);

So far so good, this works great! When I start editing a project post, the meta data is populated with the ACF fields from that project and I can add/save content to the core/paragraph blocks.

The content is saved in the database and displayed correctly in my frontend. However, whenever I change one of the ACF fields, these changes are not reflected in my Gutenberg block, not even on a page refresh.

Instead I get the message:

The content of your post doesn’t match the template assigned to your post type.

When I log the attribute values in javascript, these are the old values. Even on a page refresh. I can only see that the post content is still saved with the old attributes:

<!-- wp:domain/lead-project {"projectClient":"Testing","projectDate":"07 Jul 2013 – 04 Mar 2017","projectLocation":"Ibiza, Amsterdam"} -->

So my question basically is; what is the best practice to use/display static data that doesn’t have to be editable in the Gutenberg editor, but should update itself whenever it’s value changes?

I did found this Use Post Meta Data tutorial, but that just appends the data to the end of a block, without control over the markup plus it’s not visible in the editor.

Instead of using the RichText block, I also tried using plain jsx tags for displaying the data, but it has the same result.

Any thoughts, tips?
Thanks a lot!

EDIT: I found this article about Managing WordPress Metadata in Gutenberg, but I don’t think there’s an easy way yet to expose ACF fields in the post meta data?

select('core/editor').getEditedPostAttribute('meta')