When building custom ACF blocks for WordPress, you might need the preview to update dynamically when editors change block settings. Here’s how to implement this functionality:
The Problem
By default, ACF blocks don’t automatically update their preview when field values change in the editor. If your block contains dynamic content (like a list of posts) that depends on block settings, you’ll need to handle these updates yourself.
The Solution
We’ll create a system that:
- Detects when block settings change
- Fetches updated content
- Updates the preview in real-time
Implementation Steps
1. Set Up Your Block Structure
// block.json
{
"name": "acf/your-block",
"title": "Your Block",
"acf": {
"mode": "preview",
"renderTemplate": "template.php"
}
}
2. Create a Base Class for Content Management
class ContentManager {
constructor(container) {
this.container = container;
this.contentContainer = container.querySelector('.content-container');
// Initialize your content display logic
}
updateContent(data) {
// Your content rendering logic
}
}
3. Create a Centralized Fetch Function
async function fetchContentFromServer(options, containers = {}) {
const { container, contentContainer } = containers;
// Handle loading states
if (container) {
if (container.dataset.isLoading === "true") return;
container.dataset.isLoading = "true";
if (contentContainer) {
contentContainer.style.opacity = "0.5";
}
}
try {
const response = await fetch(ajaxurl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
action: "your_action",
nonce: your_nonce,
...options,
}),
});
const data = await response.json();
if (!data.success) {
throw new Error(data.message || "Failed to fetch content");
}
return data.data;
} finally {
// Reset loading states
if (container) {
container.dataset.isLoading = "false";
if (contentContainer) {
contentContainer.style.opacity = "1";
}
}
}
}
4. Set Up Preview Handler
function initializePreview(block) {
if (!window.acf) return;
// We only need to add this action once
if (!initializePreview.initialized) {
acf.addAction("render_block_preview", async function(block) {
// Check if this is our block
if (block[0]?.dataset?.type === "acf/your-block") {
const blockElement = block[0].querySelector(".your-block");
if (!blockElement) return;
// Create content manager instance
const contentManager = new ContentManager(blockElement);
// Get options from block data attributes
const options = {
setting1: blockElement.dataset.setting1,
setting2: blockElement.dataset.setting2,
preview: true
};
try {
const data = await fetchContentFromServer(
options,
{
container: blockElement,
contentContainer: blockElement.querySelector(".content-container")
}
);
contentManager.updateContent(data);
} catch (error) {
console.error("Preview update failed:", error);
}
}
});
initializePreview.initialized = true;
}
}
5. Initialize on Page Load
window.addEventListener("load", function() {
if (typeof acf === "undefined") return;
initializePreview();
});
Key Points to Remember
- Use a class to manage content display logic – this keeps frontend and preview rendering consistent
- Centralize your fetch logic to handle both preview and frontend requests
- Use data attributes to pass settings from PHP to JavaScript
- Handle loading states and errors gracefully
- Only initialize preview handling once
Common Pitfalls
- Don’t create separate rendering logic for preview and frontend
- Remember to handle loading states to prevent multiple simultaneous requests
- Make sure your PHP template includes necessary data attributes
- Consider error handling and fallback content
- This pattern can be adapted for any ACF block that needs dynamic preview updates based on field changes.