• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Building a Reactive Frontend Framework inside Full Site Editing (FSE) Block Themes and theme.json Using Custom Action and Filter Hooks

Building a Reactive Frontend Framework inside Full Site Editing (FSE) Block Themes and theme.json Using Custom Action and Filter Hooks

Leveraging `theme.json` and Custom Hooks for Reactive FSE Components

Full Site Editing (FSE) in WordPress, powered by block themes and the `theme.json` configuration file, offers a robust foundation for building dynamic and customizable websites. However, achieving truly reactive frontend behavior—where UI elements update instantly based on data changes or user interactions without full page reloads—often requires going beyond the standard block API. This post details how to architect a reactive frontend component system within the FSE paradigm by strategically employing custom action and filter hooks, and by deeply integrating with `theme.json` for dynamic styling and configuration.

Defining Reactive Component State and Actions

The core of any reactive system lies in managing state and defining how that state can be mutated. In a WordPress context, we can abstract this by creating custom JavaScript classes or objects that hold component state and expose methods for updating it. These methods will be the “actions” that trigger UI re-renders. For this example, let’s imagine a simple “Live Counter” component that increments or decrements a value displayed on the frontend, with its initial value and step controlled via `theme.json`.

JavaScript State Management and Actions

We’ll define a JavaScript class to encapsulate the counter’s logic. This class will hold the current count and provide methods to modify it. Crucially, it will also dispatch custom events that our frontend rendering logic can listen to.

class LiveCounter {
    constructor(initialValue = 0, step = 1) {
        this.count = initialValue;
        this.step = step;
        this.listeners = [];
        this.dispatchUpdate(); // Initial dispatch
    }

    // Method to subscribe to state changes
    subscribe(callback) {
        this.listeners.push(callback);
        // Immediately call back with current state
        callback(this.count);
    }

    // Method to unsubscribe
    unsubscribe(callback) {
        this.listeners = this.listeners.filter(listener => listener !== callback);
    }

    // Dispatch an update event to all subscribers
    dispatchUpdate() {
        this.listeners.forEach(listener => {
            try {
                listener(this.count);
            } catch (error) {
                console.error("Error in listener callback:", error);
            }
        });
    }

    // Action: Increment the counter
    increment() {
        this.count += this.step;
        this.dispatchUpdate();
    }

    // Action: Decrement the counter
    decrement() {
        this.count -= this.step;
        this.dispatchUpdate();
    }

    // Action: Set a specific value
    set(value) {
        this.count = value;
        this.dispatchUpdate();
    }

    // Get current count
    getCount() {
        return this.count;
    }

    // Get current step
    getStep() {
        return this.step;
    }
}

Integrating with `theme.json` for Configuration

The power of FSE lies in `theme.json`, which acts as a central configuration hub for styles and settings. We can expose custom settings within `theme.json` that our JavaScript components can read. This allows theme developers to control component behavior and appearance directly from the WordPress Customizer or block editor interface, without touching code.

Defining Custom Settings in `theme.json`

We’ll add a custom section to `theme.json` to define the initial value and step for our `LiveCounter`. These values will be accessible via the WordPress REST API and can be enqueued into our JavaScript.

{
    "version": 2,
    "settings": {
        "color": {
            // ... existing color settings
        },
        "layout": {
            // ... existing layout settings
        },
        "custom": {
            "liveCounter": {
                "initialValue": 10,
                "step": 5,
                "textColor": "#333333",
                "backgroundColor": "#f0f0f0"
            }
        }
    },
    "styles": {
        // ... existing styles
    }
}

Enqueuing `theme.json` Data into JavaScript

To make the `theme.json` settings available to our frontend JavaScript, we need to enqueue them. This is typically done using `wp_localize_script` or by directly embedding JSON data into a script tag. A common and clean approach is to use a custom REST API endpoint or to filter the `wp_get_global_settings()` output.

Using `wp_get_global_settings()` and `wp_add_inline_script`

We can hook into `wp_get_global_settings()` to augment the data passed to the frontend. Then, we’ll use `wp_add_inline_script` to inject this data into our JavaScript bundle.

/**
 * Enqueue custom scripts and localize theme settings.
 */
function my_theme_enqueue_scripts() {
    // Register and enqueue your main JavaScript file
    wp_enqueue_script(
        'my-reactive-frontend',
        get_template_directory_uri() . '/assets/js/reactive-frontend.js',
        array( 'wp-element', 'wp-blocks', 'wp-components', 'wp-i18n' ), // Dependencies
        filemtime( get_template_directory() . '/assets/js/reactive-frontend.js' ),
        true // Load in footer
    );

    // Get global settings and add our custom data
    $global_settings = wp_get_global_settings();
    $custom_settings = isset( $global_settings['custom'] ) ? $global_settings['custom'] : [];

    // Ensure our specific settings exist, with fallbacks
    $live_counter_settings = wp_parse_args( $custom_settings['liveCounter'] ?? [], [
        'initialValue' => 0,
        'step'         => 1,
        'textColor'    => '#000000',
        'backgroundColor' => '#ffffff',
    ] );

    // Localize the script with our settings
    wp_localize_script(
        'my-reactive-frontend',
        'themeSettings',
        array(
            'liveCounter' => $live_counter_settings,
            'ajaxUrl'     => admin_url( 'admin-ajax.php' ), // Useful for future AJAX actions
        )
    );
}
add_action( 'wp_enqueue_scripts', 'my_theme_enqueue_scripts' );

/**
 * Filter global settings to include custom values.
 * This is an alternative/complementary approach to wp_localize_script
 * if you want the data available globally in JS without a specific handle.
 */
function my_theme_filter_global_settings( $settings ) {
    if ( ! isset( $settings['custom']['liveCounter'] ) ) {
        // Fallback if not already defined or for development
        $settings['custom']['liveCounter'] = [
            'initialValue' => 0,
            'step'         => 1,
            'textColor'    => '#000000',
            'backgroundColor' => '#ffffff',
        ];
    }
    return $settings;
}
// Note: This filter is often applied in the block editor context or for global JS variables.
// For frontend scripts, wp_localize_script is generally preferred for clarity.
// add_filter( 'option_wp_global_settings', 'my_theme_filter_global_settings' );

Implementing Frontend Rendering and Reactivity

With our JavaScript state manager and configuration in place, we can now build the frontend component. This involves creating a DOM element, attaching event listeners to our `LiveCounter` instance, and updating the DOM whenever the state changes.

DOM Manipulation and Event Binding

We’ll create a JavaScript file (e.g., `reactive-frontend.js`) that initializes our `LiveCounter` and renders the UI. The rendering function will be called initially and then again every time the `LiveCounter` state updates.

// Assuming themeSettings is available globally from wp_localize_script
// and LiveCounter class is defined in the same scope or imported.

// Get settings from localized script
const liveCounterConfig = themeSettings.liveCounter || {
    initialValue: 0,
    step: 1,
    textColor: '#000000',
    backgroundColor: '#ffffff'
};

// Instantiate the LiveCounter
const counterInstance = new LiveCounter(liveCounterConfig.initialValue, liveCounterConfig.step);

// --- Rendering Logic ---
const renderCounter = (currentCount) => {
    // Find or create the main counter element
    let counterElement = document.getElementById('live-counter-display');
    if (!counterElement) {
        counterElement = document.createElement('div');
        counterElement.id = 'live-counter-display';
        counterElement.style.padding = '20px';
        counterElement.style.border = '1px solid #ccc';
        counterElement.style.textAlign = 'center';
        counterElement.style.fontFamily = 'sans-serif';
        counterElement.style.color = liveCounterConfig.textColor;
        counterElement.style.backgroundColor = liveCounterConfig.backgroundColor;
        document.body.appendChild(counterElement); // Append to body or a specific container
    }

    // Update the display
    counterElement.innerHTML = `
        <h3>Live Counter</h3>
        <p style="font-size: 2em; margin: 10px 0;">${currentCount}</p>
        <button id="increment-btn" style="margin: 5px; padding: 10px;">+</button>
        <button id="decrement-btn" style="margin: 5px; padding: 10px;">-</button>
    `;

    // Re-attach event listeners after rendering to ensure they exist
    attachEventListeners();
};

// --- Event Binding ---
const attachEventListeners = () => {
    const incrementButton = document.getElementById('increment-btn');
    const decrementButton = document.getElementById('decrement-btn');

    if (incrementButton) {
        // Remove existing listeners to prevent duplicates if render is called multiple times
        incrementButton.removeEventListener('click', handleIncrement);
        incrementButton.addEventListener('click', handleIncrement);
    }

    if (decrementButton) {
        decrementButton.removeEventListener('click', handleDecrement);
        decrementButton.addEventListener('click', handleDecrement);
    }
};

// Event Handlers
const handleIncrement = () => {
    counterInstance.increment();
};

const handleDecrement = () => {
    counterInstance.decrement();
};

// --- Subscription ---
// Subscribe to state changes and re-render
counterInstance.subscribe(renderCounter);

// Initial render is handled by the subscription's initial call.
// If you need to render before the subscription is active, call renderCounter directly:
// renderCounter(counterInstance.getCount());

Advanced: Custom Action and Filter Hooks for Extensibility

To make our reactive system truly extensible and integrate seamlessly with the WordPress ecosystem, we can define custom action and filter hooks. This allows other plugins or theme files to hook into our component’s lifecycle or modify its behavior.

Defining Custom PHP Hooks

We can define PHP hooks within our theme’s `functions.php` or a dedicated plugin. These hooks can be fired at various stages, such as before initialization, after state changes, or when rendering.

/**
 * Fires before the LiveCounter component is initialized.
 *
 * @param array $config The configuration array for the LiveCounter.
 */
do_action( 'my_theme_live_counter_before_init', $live_counter_settings );

/**
 * Fires after the LiveCounter component's state has been updated.
 *
 * @param int $new_count The new count value.
 * @param int $step The step value used for the update.
 */
do_action( 'my_theme_live_counter_after_update', $new_count, $step );

/**
 * Filters the HTML output of the LiveCounter component.
 *
 * @param string $html The generated HTML for the counter.
 * @param int $current_count The current count value.
 */
$rendered_html = apply_filters( 'my_theme_live_counter_render_html', $rendered_html, $current_count );

Hooking into JavaScript Events

Similarly, we can dispatch custom JavaScript events that can be caught by other scripts. This is a more idiomatic JavaScript approach to extensibility.

// Inside the LiveCounter class, after dispatchUpdate:
dispatchUpdate() {
    // Dispatch a custom DOM event
    const updateEvent = new CustomEvent('live-counter-update', {
        detail: { count: this.count, step: this.step }
    });
    document.dispatchEvent(updateEvent); // Dispatch globally or to a specific element

    // ... existing listener dispatch ...
}

// In reactive-frontend.js or another script:
document.addEventListener('live-counter-update', (event) => {
    console.log('Live counter updated via DOM event:', event.detail);
    // Perform additional actions based on the update
    // For example, update another UI element, trigger an AJAX call, etc.
});

// Example of hooking into the rendering process
const originalRenderCounter = renderCounter;
renderCounter = (currentCount) => {
    let html = `
        <h3>Live Counter</h3>
        <p style="font-size: 2em; margin: 10px 0;">${currentCount}</p>
        <button id="increment-btn" style="margin: 5px; padding: 10px;">+</button>
        <button id="decrement-btn" style="margin: 5px; padding: 10px;">-</button>
    `;

    // Apply filters if they exist (simulated)
    if (typeof wp_apply_filters !== 'undefined') { // Assuming a WP-like filter system in JS
        html = wp_apply_filters('my_theme_live_counter_render_html', html, currentCount);
    }

    originalRenderCounter(currentCount); // Call the original rendering logic
};

Advanced Diagnostics and Debugging

When building complex reactive systems, debugging can become challenging. Here are some advanced diagnostic techniques:

1. State Inspector in Browser DevTools

Leverage the browser’s developer console extensively. You can:

  • Log state changes within the `LiveCounter` class’s `dispatchUpdate` method.
  • Use `console.table()` to inspect arrays or objects passed to listeners.
  • Set breakpoints in your rendering logic and event handlers to step through execution.
  • Inspect the `themeSettings` object in the console to verify that `theme.json` data is loaded correctly.
// Example: Logging state changes in LiveCounter
class LiveCounter {
    // ... constructor and other methods ...

    dispatchUpdate() {
        console.log('LiveCounter state update:', { count: this.count, step: this.step }); // Log state
        this.listeners.forEach(listener => {
            try {
                listener(this.count);
            } catch (error) {
                console.error("Error in listener callback:", error);
            }
        });
    }
    // ...
}

2. WordPress Debugging Tools

Ensure `WP_DEBUG` and `WP_DEBUG_LOG` are enabled in your `wp-config.php` for PHP errors. For JavaScript, browser console logs are primary. You can also use PHP’s `error_log()` to send messages to the WordPress debug log from your PHP hooks.

// In your PHP hook, for example:
function my_theme_live_counter_after_update_handler( $new_count, $step ) {
    error_log( sprintf( 'Live counter updated. New count: %d, Step: %d', $new_count, $step ) );
    // You can also log the result of apply_filters here
}
add_action( 'my_theme_live_counter_after_update', 'my_theme_live_counter_after_update_handler', 10, 2 );

3. Network Tab Analysis

Monitor the Network tab in your browser’s dev tools to check:

  • If your JavaScript files are loading correctly (status 200).
  • If any AJAX requests (if you implement them later) are failing (status 4xx, 5xx).
  • The response from `admin-ajax.php` or REST API endpoints if used for dynamic data fetching.

4. Inspecting `theme.json` Loading

Verify that `theme.json` is being parsed and its values are accessible. You can do this by:

  • Checking the `themeSettings` object in the JavaScript console.
  • Using `wp_debug_backtrace_summary()` in PHP to trace where `wp_get_global_settings()` is being called and what data it contains.
  • In the browser, you can often inspect the global `wp` object or specific localized script variables.

Conclusion

By combining custom JavaScript state management, strategic use of `theme.json` for configuration, and the power of custom action/filter hooks (both in PHP and JavaScript), you can build sophisticated, reactive frontend components within WordPress’s FSE framework. This approach not only enhances user experience through dynamic UIs but also provides a robust and extensible architecture that adheres to WordPress best practices.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Top 100 Developer Tooling and Productivity SaaS Ideas to Launch in 2026 to Boost Organic Search Growth by 200%
  • Top 100 Developer-Centric Code Snippet Managers and Customization Plugins to Double User Engagement and Session Duration
  • Top 5 API Monetization Frameworks and Gateway Strategies for Developers to Minimize Server Costs and Load Overhead
  • Top 50 Automated PDF & Document Generation Tool Ideas for Developers to Minimize Server Costs and Load Overhead
  • Top 50 Premium Newsletter and Subscription Business Models for Devs for High-Traffic Technical Portals

Categories

  • apache (1)
  • Business & Monetization (386)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (565)
  • DevOps (7)
  • DevOps & Cloud Scaling (949)
  • Django (1)
  • Migration & Architecture (167)
  • MySQL (1)
  • Performance & Optimization (754)
  • PHP (5)
  • Plugins & Themes (225)
  • Security & Compliance (539)
  • SEO & Growth (484)
  • Server (23)
  • Ubuntu (9)
  • WordPress (22)
  • WordPress Plugin Development (7)
  • WordPress Theme Development (304)

Recent Posts

  • Top 100 Developer Tooling and Productivity SaaS Ideas to Launch in 2026 to Boost Organic Search Growth by 200%
  • Top 100 Developer-Centric Code Snippet Managers and Customization Plugins to Double User Engagement and Session Duration
  • Top 5 API Monetization Frameworks and Gateway Strategies for Developers to Minimize Server Costs and Load Overhead
  • Top 50 Automated PDF & Document Generation Tool Ideas for Developers to Minimize Server Costs and Load Overhead
  • Top 50 Premium Newsletter and Subscription Business Models for Devs for High-Traffic Technical Portals
  • Top 100 SEO and Schema Markup Plugins for Headless Decoupled Sites for Independent Web Developers and Indie Hackers

Top Categories

  • DevOps & Cloud Scaling (949)
  • Performance & Optimization (754)
  • Debugging & Troubleshooting (565)
  • Security & Compliance (539)
  • SEO & Growth (484)
  • Business & Monetization (386)

Our Products

  • School Management & Student Administration System
  • Integrated Hospital & Clinic Management System
  • Real Estate Directory & Agent Portal
  • Restaurant POS & Table Booking System
  • Retail Inventory POS & Billing System
  • Pharmacy Inventory & Clinic Billing System

Our Services

  • Vibe Engineering & AI Code Auditing Services
  • Prompt Engineering & "Vibe Coding" Workflow Consulting
  • AI-Augmented "Vibe Coding" & Rapid MVP Development
  • Figma to Shopify Liquid Theme Customization
  • Figma to WooCommerce Frontend Development
  • Figma to Magento 2 Theme Development

Copyright © 2026 · Vinay Vengala