Rus Miller

Senior Full-Stack  Web Engineer

I design and develop digital experiences that are beautiful and functional, ensuring that every user can easily navigate and enjoy them.

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:

  1. Detects when block settings change
  2. Fetches updated content
  3. 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

  1. Use a class to manage content display logic – this keeps frontend and preview rendering consistent
  2. Centralize your fetch logic to handle both preview and frontend requests
  3. Use data attributes to pass settings from PHP to JavaScript
  4. Handle loading states and errors gracefully
  5. 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.