Using The Interactivity API instead of React (part 2): The markup

In part 1, we talked about the why and getting set up. In Part 2, we’ll examine the markup between the PHP and React code.

The Archive Structure

Originally, I had a pretty bare-bones PHP file for the archive page (note the comments along the way).

<?php
/**
* Archive: Veterans
*/

wp_enqueue_script('search'); // this is the react code
get_header();
?>
<div id="search"> <!-- this is where the react app mounts -->
	<div class="container py-5">
		<div class="row">
			<div class="col">
				<div className="spinner-border text-primary" role="status">
					<span className="visually-hidden">Loading...</span>
				</div>
			</div>
		</div>
	</div>
</div>
<noscript>
<!-- notice the `noscript`, meaning you'll only see this if you have javascript straight up turned off. And it's basically an incomplete loop -->
	<?php if ( ! have_posts() ) : ?>
	<div class="container">
		<div class="row my-5 py-5">
			<div class="col">
				<p>Couldn't find any veterans! Please try again.</p>
			</div>
		</div>
	</div>
	<?php else : ?>
	<div class="container my-5 py-5">
		<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-gap-4">
			<?php while ( have_posts() ) : ?>
				<?php the_post(); ?>
			<div class="col">
				<?php get_template_part( 'template-parts/veterans/content', 'veteran-preview' ); ?>
			</div>
			<?php endwhile; ?>
		</div>
	</div>
	<?php endif; ?>
</noscript>

<?php
get_footer();

Nothing crazy, but definitely some poor non-JS fallback code. In fact, I cared so little that the php-based Veteran Preview template doesn’t even look like it’s React counterpart because the project had gotten so far along I didn’t care to maintain it. Now, let’s focus in on what changed in v3 with the interactivity API.

The New Loop

<?php
/**
* Archive: Veteran (v3)
*/

// get header
// search bar
// pagination controls
?>
<section id="results" class="my-5 container">
	<?php
	if ( have_posts() ) {
		echo '<ol class="list-unstyled row row-cols-1 row-cols-md-3 row-cols-lg-3 row-gap-4">';
		while ( have_posts() ) {
			the_post();
			echo '<li class="flex-grow-1 flex-shrink-1 col-lg-4 col-auto">';
			get_template_part( 'template-parts/veterans/content', 'veteran-preview' );
			echo '</li>';
		}
		echo '</ol>';
	} else {
		echo '<p>No veterans found.</p>';
	}
	?>
</section>
<?php
// pagination controls
// get footer

Notice how this is basically the same as the previous code snippet? Really, the only difference is that I took it outside of the <noscript> and put it inside a section. Much easier to reason about, and the Veteran Preview PHP template was updated to match the React counterpart.

To make this work, I did need to update the main query loop, but I’ll write more about that in part 3.

The Search Form

No major visual changes to the search form, but I did make a few new accessibility changes:

  1. The form is nested directly inside a <search> landmark element (previously was <section> -> <search>
  2. The filters are nested inside a <fieldset> and the <form> grew to contain them
  3. The Reset button went from a dynamically rendered <button> to a working <a> that points a user back to the archive page with no query parameters, fulfilling the javascript-less functionality.

The Pagination

some of this doesn’t work 100% without hydration, but the main bit does

Per Page <select>

Pretty straightforward here: we use a loop to create the <option>s for the select component and use the selected method to check whether it should be selected against URL Query params.

<?php
/** 
* the select
*/

$markup  = '<select name="perPage" class="btn btn-outline-dark z-2 border-0 fs-6" data-wp-on--change="actions.setPerPage">';
	$options = array( 9, 18, 27, 36 );
	foreach ( $options as $value ) {
		$markup .= sprintf( '<option value="%d" %s>%d</option>', $value, selected( $i11y->per_page, $value, false ), $value );
	}
$markup .= '</select>';

Since it lives outside of the <form>, it won’t work without Javascript, but still pretty basic PHP here.

The Current Page

The most straightforward bit of code:

<div class="current-page fw-bold fs-5 text-center">Page <span data-wp-text="state.currentPage"></span> of <span data-wp-text="state.totalPages"></span></div>

This probably could work with PHP if I had hooked into the global $wp_query to get the current page and max_num_pages but, to me, it’s non-essential and I didn’t think about it until writing this article, so I’m just going to leave it as is.

The Pagination Controls

Pagination controls are pretty straightforward here: Next Page and Previous Page. The magic bits here are in the interactivity router, but that will be part 3, so I’m just going to hint at it here:

<?php
/**
* Pagination buttons
*/

$next_url = add_query_arg( 'page', ( min( $i11y->current_page + 1, $i11y->total_pages ) ) );
$prev_url = add_query_arg( 'page', ( max( $i11y->current_page - 1, 1 ) ) );

sprintf('<a href="%s" class="%s" data-wp-on--click="actions.navigateToPage">%s</a></div>', esc_url( $button['url'] ), implode( ' ', $classes ), esc_html( $button['label'] );

The Veteran Preview Cards

Instead of a custom rest route to feed JSON to a React component, we just build out the card in PHP like we expect. Super straightforward and it reduces the need for an extra endpoint.

See something inaccurate?