Skip to content

Query & AJAX

How dynamic blocks fetch posts — the AJAX endpoints, WP_Query construction, transient caching, and the editor-side useApiData hook.

Dynamic blocks such as Post Grid need to query posts both in the editor (live preview) and on the frontend (initial render plus AJAX filtering/pagination). Styble centralises this in two PHP classes and one React hook.

The pieces

ComponentFileRole
Blocks_Queryblocks/Includes/Blocks_Query.phpRegisters and handles the wp_ajax_* endpoints.
PostQueryHandlerblocks/Includes/PostQueryHandler.phpBuilds the WP_Query arguments from block data.
Transient_Registryblocks/Includes/Utils/Transient_Registry.phpTracks query transients per post for cache clearing.
useApiDatasrc/hooks/useApiData.jsEditor-side hook that fetches query results with debouncing.

Blocks_Query and the AJAX endpoints

Blocks_Query (namespace ShapedPlugin\StyblePro\Includes) is a singleton booted from Block_Init. Its init() registers the AJAX actions:

php
public function init() {
	Transient_Registry::maybe_migrate_legacy_option();

	// Editor-only: authenticated users with edit_posts.
	add_action( 'wp_ajax_styble_post_block_post_query', array( $this, 'styble_post_block_post_query' ) );
	if ( is_admin() ) {
		add_action( 'wp_ajax_styble_post_block_all_post_query', array( $this, 'styble_post_block_all_post_query' ) );
	}
	add_action( 'wp_ajax_styble_post_block_meta_data_query', array( $this, 'styble_post_block_meta_data_query' ) );

	// Public post-grid live filter (nonce required; no postmeta exposure).
	add_action( 'wp_ajax_styble_ajax_filter_posts', array( $this, 'styble_ajax_filter_posts' ) );
	add_action( 'wp_ajax_nopriv_styble_ajax_filter_posts', array( $this, 'styble_ajax_filter_posts' ) );
}
ActionAccessPurpose
styble_post_block_post_querywp_ajax_ (logged-in)Editor post query for previews.
styble_post_block_all_post_querywp_ajax_ (admin only)Fetch all posts for selection controls.
styble_post_block_meta_data_querywp_ajax_ (logged-in)Metadata/taxonomy queries for controls.
styble_ajax_filter_postswp_ajax_ + wp_ajax_nopriv_Public live filtering & pagination of the Post Grid.

Security

The authenticated handlers verify a nonce and editor capability before running. require_authenticated_query_access() checks the sp_styble_block_nonce nonce and rejects invalid requests:

php
if ( ! wp_verify_nonce( $nonce, 'sp_styble_block_nonce' ) ) {
	wp_send_json_error( array(
		'code'    => 'invalid_nonce',
		'message' => __( 'Session expired. Please reload the page.', 'styble-pro' ),
	) );
}

The public filter endpoint also requires a nonce and deliberately does not expose post-meta. Responses use wp_send_json_success() / wp_send_json_error().

PostQueryHandler

PostQueryHandler (blocks/Includes/PostQueryHandler.php) turns block query data into a WP_Query. Its static query() method is the entry point:

php
public static function query( $query_data, $type = null, $block_id = '' ) {
	// … build $args (post type, taxonomy, meta, order, pagination) …
	$args = apply_filters( 'styble_pro_query_args', $args, $block_name, $query_data );
	// … run WP_Query and shape the response …
}

The styble_pro_query_args filter lets you adjust the final query arguments for any block before the query runs — see Hooks & Internationalization. It supports custom post types, taxonomy filtering, metadata queries, ordering, and pagination.

Transient caching with key tracking

Query results are cached in WordPress transients per block instance. So that the right caches can be purged when content changes, each transient is registered against the post it belongs to via Transient_Registry::track_for_post(). The registry stores the mapping of transient keys to post IDs.

On save_post and deleted_post, Block_Init::clear_cache() calls into the registry to clear that post's transients (skipping autosaves and revisions):

php
// blocks/Block_Init.php
add_action( 'save_post', array( $this, 'clear_cache' ), 10, 1 );
add_action( 'deleted_post', array( $this, 'clear_cache' ), 10, 1 );

Transient_Registry also exposes targeted helpers such as clear_post_grid_query_transients() and clear_all_post_grid_query_transients() for finer-grained invalidation. The same registry backs the dynamic CSS cache.

The useApiData hook

On the editor side, useApiData (src/hooks/useApiData.js, exported from src/hooks/index.js) fetches query results based on the block's attributes and keeps the preview in sync as settings change. It debounces requests so dragging a slider or typing does not fire a request per keystroke:

js
/**
 * Debounce delay in ms — prevents rapid fire Ajax requests
 * when the user drags a slider or types quickly.
 */
const DEBOUNCE_MS = 300;

const useApiData = ( attributes, options = {} ) => {
	const { enabled = true } = options;
	// … derives a dependency object from query-related attributes,
	//    debounces, calls the AJAX endpoint, and returns posts + state.
};

It returns the fetched posts together with state such as isLoading, totalPages, and postCount, which the block uses to render previews and to decide whether to attach pagination.

Pagination as a child block

Pagination attaches conditionally: only when totalPages > 1 and pagination is enabled. The useAddChildBlock hook handles attaching the pagination child block based on the loaded data. See the user-facing Ajax Pagination page.

Released under the GPL-2.0-or-later License.