I remember back when I learned how to develop for WordPress, I spent weeks in the Codex, learning the template hierarchy, the template tags, and so on. The Codex was like my Hitchhiker’s Guide To The Galaxy: they might as well have printed “DON’T PANIC” on the front, because it kept me learning at a comfortably steady pace. These days it feels more like I’m stuck in Dictionopolis from The Phantom Tollbooth. Reading through documentation for new technologies is like I’m just staring at heaps of words trying to force my brain into understand just what “ComponentWillUnmount” means in human English. The trouble is that as we get new technologies, documentation tends to lag behind development. The docs I’m seeing these days for things like the WordPress REST API or React don’t have that same friendly quality that the Codex did, yet. Fortunately, we can turn to trailblazers: people who have gone before us, figured out how to use it, and then turned around and shared that knowledge. If it weren’t for Wes Bos, I wouldn’t even be writing this article. His “React for Beginners” course recaptured that same steady, comfortable way of learning for me. Eventually, I learned how to convert a plugin from using jQuery to using the WordPress REST API and React JS. I learned a lot along the way, so here’s my take, in human English: Getting to Know the Lingo is the Hardest Part When I started looking around for a “beginner’s guide to the WordPress REST API,” I was confronted with a horde of new vocab terms: “route,” “endpoint,” “RESTful,” “CRUD,” and so on. In short, I fell down a rabbit hole of foundational knowledge when all I wanted was practical knowledge. I wanted to figure out how to use it. After doing a lot of reading, I realized there were only two terms I had to figure out: “routes” and “endpoints.” The crazy thing is, these terms are not as technical or complicated as they sound. A route is a URL. An endpoint is a PHP function. Here’s how they work together to get data from WordPress. How Routes and Endpoints Work Normally, we use PHP to get data from the WordPress database. The trouble with that is that it’s a server-side thing. If any data changes, you’ll have to reload the page to see it. The REST API opens things up to the front-end: we can get data from WordPress using a HTTP connection (a URL) instead of using PHP. This way, you can use JavaScript to get data from the WordPress database without reloading the page. This URL that gets and retrieves data is called a route. It’s a special URL that you have to register. WordPress comes with a few pre-registered, but the real power is in registering custom routes. This was my key misconception number one: just like custom post types or meta, you can register as many routes as you want. My key misconception number two was thinking that these routes can only return specific types of data. FALSE. A custom route can return whatever data you want. When you use register_rest_route to define what the route will be, you also give it a PHP function to run when the route is loaded. That PHP function is known as the endpoint. This leads me to key misconception number three: I assumed using the REST API meant you had to use JavaScript 100% of the time. When the route (URL) is loaded, WordPress calls the endpoint (PHP function that returns data). Global State with React JS Makes it Easy to See What’s Going On If you read up on React and similar JavaScript frameworks, there’s a lot of talk about “state.” All “state” means, really, is “what’s going on in this application right now?” Is the modal window open or closed? Which slide in the slideshow is currently displayed? State keeps track of things that change. This means that you can handle state whether you’re working in jQuery or in React (key misconception number four, if you’re keeping track at home). The State pane in the React panel shows the global state for the whole application. The key difference is that React centralizes all of the things that can change into one place, the state object. Grouping these details together makes it much easier to see what’s going on across the entire application. It also makes it much easier to change. With the React Developer Tools in Chrome, you get a new tab in the Dev Toolbar called React, where you can inspect and manually change the state to see how it effects your project. Getting Started with React and the WordPress REST API Actually getting started with React and the WordPress REST API can be a challenge, even if you’re familiar with both technologies. For example, I wanted to refactor the Admin Command Palette plugin that I and a couple other guys at my job made. It’s a universal WordPress admin search modal. The original version runs on jQuery and works decent, but has a fatal design flaw: it can’t scale at all. The Admin Command Palette search in action. When we built it, I had looked at other plugins that used AJAX to power the search, and they seemed slow. I had the bright idea to localize all the search data to get around this. While that’s not terrible when you’re dealing with less than 500 posts, try doing it with 5,000! All that data was echoed out in the footer and made the WP-Admin take forever to load. I had cleverly created the exact problem I was trying to avoid. But I’m a smarter developer today (which is what all developers tell themselves), and figured rewriting it in React would make it cleaner and faster. When I first got started, I fiddled with React for a bit, but was having a hard time figuring out how exactly to connect it to the API. So I went out and found another trailblazer. Go Pangolin created a boilerplate React / WP API plugin called WP-Reactivate that was exactly what I needed. I was immediately drawn to their Fetch API example, which demonstrates the coupling from React to the WordPress REST API. Eventually, I wound up with this: fetch(`${acp_object.api_search_url}/?command=${this.state.search}`, { credentials: 'same-origin', method: 'GET', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': acp_object.api_nonce, }, }) .then(response => response.json()) .then( (json) => this.setState({ results: json.results, count: json.count, }), (err) => console.log('error', err) ); A few notes on what we’re looking at here: acp_object is a localized script (data that you set up with PHP but make available for JavaScript) ${acp_object.api_search_url} is the WP API Route that I registered using register_rest_route. ?command=${this.state.search} is important: I used this to pass a parameter from my JavaScript into the PHP function, which I retrieved with get_param( 'command' ); X-WP-Nonce is also important: with this, you make the connection from JavaScript to PHP secure. this.setState is where I take the data that came back from the PHP function and update the React application. Debugging PHP through JavaScript My last practical tip is on debugging. When you’re using React, it’s not always easy to see what’s wrong if you’re getting the wrong data back from your PHP function. When I was refactoring the Admin Command Palette, the “XHR” tab (under Network in Chrome’s Web Inspector) proved invaluable. Here you can see the exact response as it comes back from PHP, before React intercepts it. What this means is that you can use print_r() statements to test variables inside of PHP and you’ll see the output on the XHR tab. Yeah, it’s hacky as hell, but it works. You could also add temporary parameters to the response data that gets sent back to React. In my example only a search results array and the total count get passed back, but when I was developing it, I threw in things like “post_args,” “search_filters,” or “command_type” all willy-nilly. That’s what development is for. It’s a safe place to break whatever you need to in order to make your code better.
One thing you should know about me is that while I’m pretty savvy when it comes to building things for the web today, I’m still pretty new to it. I spent my childhood building theme parks in Roller Coaster Tycoon, not Flash games for Newgrounds. Rather, I gravitated toward web development during the final year of college and because of that, I am a bit of a superstitious developer. That is, I tend to fall into the trap of thinking any code I don’t understand backwards and forwards must be written with black magic. In particular, the PHP function sprintf held sway over my soul for quite some time before I bothered to learn what it did. It’s my belief that there are a lot of developers like me out there, with the mindset of: “This thing works pretty well without me learning about it, so I’ll just leave it alone.” What I’ve found is that as soon as I take a good look at an intimidating piece of code, it starts to make sense. But instead of learning from that, I go and get petrified over some other piece of code the very next week. If you get nothing else from this article, remember this: don’t be a superstitious developer! Fight the fear! It’s not as scary as you think. To demonstrate, I’d like to talk more about an important but often overlooked part of WordPress: the search. Demystifying the WordPress Search The biggest problem with the WordPress search isn’t in the code. The problem is that nobody is willing to give it a chance. Searching for “How does WordPress search work” in Google is a frustrating experience. Seven out of the first ten results are “How to replace WordPress search.” Developers have decided it’s not worth understanding even the basics of WordPress search. This is how a WordPress search works: you give the search a keyword, and it tries to match it in a post title, post excerpt, or post content. The easiest way to unpack this is by looking at the request property of $wp_query: SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts WHERE 1=1 AND (((wp_posts.post_title LIKE '%wat%') OR (wp_posts.post_excerpt LIKE '%wat%') OR (wp_posts.post_content LIKE '%wat%'))) So search is just another WordPress query, just like the ones that we use all the time to get posts! Like the proverb about wrenches vis-á-vis ball-dodging, if you can configure a post query, you can configure a search query. Why Would You Want to Improve the Search Query? The search query has been giving developers angst, poor sleep and skin lesions for years now (well, angst definitely, the others are just a fair guess). There’s nearly 8,000 questions on Stack Overflow alone regarding “WordPress search.” The classic issue you’ll run in to is what I would call “extending” the search query: making it search more than just the post title, excerpt or content. The reason that that can be difficult is that searching over things like custom meta keys can balloon out of control quickly. The Danger of Over-Scaling the Search Query In SQL terms, that means that first you’d have to combine the wp_posts table with the wp_postmeta table (called a “join”) and then search over meta values as well, like this: SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts INNER JOIN wp_postmeta ON wp_posts.ID = wp_postmeta.post_id WHERE 1=1 AND (((wp_posts.post_title LIKE '%wat%') OR (wp_posts.post_excerpt LIKE '%wat%') OR (wp_posts.post_content LIKE '%wat%') OR (wp_postmeta.meta_value LIKE '%wat%'))) This query doesn’t really scale well because there are a ton of junky meta items, like “_edit_lock” or “_edit_last,” or “_thumbnail_id,” in other words, fields that don’t have words to them. Fields that are useless to search over. Scaling the Search Query Appropriately Now, if every post had about 10 custom meta values that you didn’t need to search over, that could get out of hand pretty quick. That’s why something like this is more handy: SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts INNER JOIN wp_postmeta ON wp_posts.ID = wp_postmeta.post_id WHERE 1=1 AND (((wp_posts.post_title LIKE '%wat%') OR (wp_posts.post_excerpt LIKE '%wat%') OR (wp_posts.post_content LIKE '%wat%') OR (wp_postmeta.meta_key = 'genre' AND wp_postmeta.meta_value LIKE '%wat%'))) The problem with that is that you’d then have to pick and choose meta keys to search over, basically creating a whitelist of keys. I don’t know about you, but that sounds like a lot to manage in the long run. The good news is, there are plenty of plugins out there that help you do just that. If you have a lot of text-heavy custom meta on your site, I’d definitely recommend getting a search extending plugin to help your users find what they’re looking for. The Case for Narrowing the Search What you don’t hear about as often is narrowing the WordPress search, sometimes called “faceted search.” What you have to remember about the WordPress search query is that it’s not smart. The search query is just a simple text comparison, it’s not a Google algorithm. Therefore, there’s a danger that you could end up hitting users with so many matches that it’s impossible for them to figure out which page has the information they’re looking for. More importantly, your search page is your last line of defense for website navigability. Chances are that if users are going to the search box, it’s because they can’t find what they’re looking for anywhere else and they’re gambling that the search will lead them to the right page faster. In that case, there are a couple of situations where I think it could make sense to narrow the search: A specific post type. A specific post type plus a taxonomy term. A specific page and its children. Narrowing the Search Query for a Post Type Setting the WordPress search form to limit its search to a specific post type is a matter of changing the action attribute. Normally, a WordPress search form would look like this: <form role="search" method="get" class="search-form" action="https://www.joshsmoviewebsite.com/"> To modify it to search over a custom post type that has an archive named “movies,” you would add the archive slug on to the end of the action URL, like this: <form role="search" method="get" class="search-form" action="https://www.joshsmoviewebsite.com/movies/"> You can narrow the search down further with custom taxonomies. For instance, I want to let people filter search results by movie genre as well. To do that, I’d include a select box for the taxonomy named genre inside of the search form using wp_dropdown_categories. The resulting search form markup would look like this: <form role="search" method="get" class="searchform" action="https://www.joshsmoviewebsite.com/movies"> <label class="screen-reader-text" for="s">Search for:</label> <input type="text" value="" name="s" id="s" size="1" placeholder="Search Movies"> <select name="genre" id="genre" class="postform"> <option value="indiana-jones">Indiana Jones</option> <option value="not-indiana-jones">Other</option> </select> <button type="submit">Find Movies</button> </form> This way, WordPress will add the genre value to the search query string, like this: https://www.joshsmoviewebsite.com/movies?s=crusade&genre=indiana-jones And take a look at how that changes the SQL request: SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts LEFT JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id) WHERE 1=1 AND ( wp_term_relationships.term_taxonomy_id IN (81) ) AND (((wp_posts.post_title LIKE '%crusade%') OR (wp_posts.post_excerpt LIKE '%crusade%') OR (wp_posts.post_content LIKE '%crusade%'))) AND wp_posts.post_type = 'movie' AND (wp_posts.post_status = 'publish') GROUP BY wp_posts.ID ORDER BY wp_posts.post_title LIKE '%crusade%' DESC, wp_posts.post_date DESC LIMIT 0, 10 Not only does the post type have to be a movie, but it also has to have a taxonomy term with an ID of 81 for our Indiana Jones genre. Narrowing the Search Query Based on Page Ancestor I ran in to a use case for this while the company I work at was building a website for our city, Winter Haven. As part of the project, we combined 6 websites into one. The problem with that, though, is that we wound up with a lot of content; more than 250 pages the last time I checked. To make search manageable for users, I worked on a way to limit the search based on the page ancestor. For example, if you search the Fire Department section, you’ll only get results from sub-pages of the top-level Fire Department page. By default, the WordPress search scans through all posts in the database for matches, but you can limit this by adding an action to pre_get_posts. First, I added a hidden input with the highest-ancestor page ID in the search form: <?php if ( is_page() ) { $current_post = get_post(); $ancestors = get_post_ancestors( $current_post ); $highest_ancestor_id = array_pop( $ancestors ); echo '<input type="hidden" name="section" value="' . $highest_ancestor_id; . '">'; } ?> Then, I adjusted the query in pre_get_posts like this: add_action( 'pre_get_posts', function ( $query ) { if ( $query->is_admin ) { return $query; } if ( $query->is_search ) { if ( isset( $_REQUEST['section'] ) ) { $section_id = $_REQUEST['section']; } else { return $query; } // Get all the pages to filter against. $my_wp_query = new WP_Query(); $all_wp_pages = $my_wp_query->query(array('post_type' => 'page', 'posts_per_page' => '-1')); // Get the IDs for all child pages of this section. $children = get_page_children( $section_id, $all_wp_pages ); if ( ! empty( $children ) ) { $child_ids = array(); foreach ( $children as $child ) { $child_ids[] = $child->post_id; } $query->set( 'post__in', $child_ids ); } } This way, users could choose to search over specific sections of the website, like Library or Parks & Recreation, rather than searching the entire site all the time. What I Learned From Working with the WordPress Search Like I said earlier, the first thing I learned is to not be intimidated by code just because I don’t know how it works. Deciphering code and figuring out how it all fits together makes us better developers. The second thing I learned is that the WordPress search isn’t bad, it’s just simple. And like most things about WP, the search is very extensible. You can both broaden the search to include more fields than the default, or narrow results to a specific post type, taxonomy or even a page-specific section of your site. But most of all, I learned to stop leaving the WordPress search as an afterthought. Take care of your website visitors by putting some thought into how you can optimize search for them.
I’ve been developing WordPress themes and plugins for the past 6 years, and in that time, I’ve learned a lot about how to efficiently centralize and organize WordPress theme files. For a long time, I thought that using Template Files was the right way to organize my WordPress theme– just whip up a post-type-specific template-file, like archive-{post_type}.php. Following the Template Hierarchy led me to a very decentralized setup, which can be inflexible and difficult to maintain. The Danger of Relying on WordPress Template Conditionals For example, on just about every project I have a file called header-section.php, where I put a section title, breadcrumbs, and maybe a background photo. Normally, whenever I needed to change something in section header out based on the post type, I relied on Template Conditionals. So if I wanted to change out the section title on the events archive, I’d write a template conditional like is_post_type_archive( ‘events' ) . But if I’ve got a taxonomy archive for events too, I’d have to couple that on there as well, like this: is_post_type_archive( ‘event' ) || is_tax( ‘event-category' ) . This eventually led me to create themes that were organized entirely around the markup, not the layout. Maintenance and minor updates were a challenge for other developers: changing out the section title “Events” for “Calendar” is simple, but only if you already know that header-section.php exists. This is a basic example, but imagine this organizational pattern spread across a site with 13 custom post types. It eventually got to the point where I could no longer be sure that the code we were writing was 100% consistent and 100% reliable. Other Issues with Markup-First Organization I also ran into trouble with code repetition; that is, repeating other “supporting” code, like centered divs. It got to the point where I wasn’t sure which layouts had what code, so I started layering more code on top of existing code, which led to a bizarre patchwork of code held together by prayers and ‘if’ statements. Using a Layout-First Approach I eventually got fed up with my overuse of WordPress Template Conditional tags, and started thinking about what I could do to only use 1 Template Conditional of each type per site. Using a Theme Wrapper to Centralize Supporting Markup First, I went an found a Theme Wrapper technique from the Sage theme by Roots: this allowed me to centralize all of my “supporting” markup into one file that every layout will use. This includes structural divs that us developers normally just stuff into header.php or footer.php, like a .main div, a .content div, and so on. This way, every layout on the site will use the same basic structure. Using Custom Actions to Insert Layout-Specific Code Next, I added custom actions in between every div in base.php, as well as actions in the WordPress Loop in `index.php`. For example, I have: before_webpage_div before_wrapper_div before_main_div before_content_div before_index before_loop before_content after_content after_loop after_index after_content_div after_main_div after_wrapper_div after_webpage_div That’s a lot, for sure, but the important thing to note is that the custom actions are based on the structure. So if the structure changes, so do the actions. The point of building custom actions into the theme is so that we can output any markup we need to at specific areas throughout different layouts and views. For example, I can: Hook a Google Tag Manager script to output after the opening body tag using the before_webpage_div action. Add pagination at the bottom of archive pages using the after_loop action. Remove the sidebar action normally hooked into after_content_div on specific layouts by using remove_action. We don’t have this same sort of flexibility with Template Conditionals: the choice is to hardcode or hack around. Tying Things Together With Layout Classes Once I started using theme actions and filters instead of layout-specific conditionals, I realized that the potential went much farther than just keeping my template files a bit cleaner. For instance, here’s what one of my layout files looked like when I was just getting started: <?php add_action( 'before_wrapper_div', 'client_get_header_int', 20 ); function client_get_header_int() { if ( ! is_front_page() ) { get_template_part( 'partials/header-int' ); } } add_action( 'after_content_div', 'client_interior_get_sidebar', 20 ); function client_interior_get_sidebar() { if ( ! is_front_page() ) { get_sidebar(); } } ); Compare that with this layout-specific PHP class: <?php namespace CLIENT; class Interior { public static function init() { add_action( 'wp', function () { if ( ! is_front_page() ) { self::hook_wordpress(); } } ); } public static function hook_wordpress() { add_action( 'before_wrapper_div', [ __CLASS__, 'get_header_int' ], 20 ); add_action( 'after_content_div', [ __CLASS__, 'get_sidebar' ], 20 ); } /** * Gets the interior header if we're not on the home page. */ public static function get_header_int() { get_template_part( 'partials/header-int' ); } /** * Gets the sidebar. */ public static function get_sidebar() { get_sidebar(); } } Interior::init(); The result is the same even though the way we get there is different. The advantages to using a PHP Class are: No function namespacing required, namespace the class instead. One layout conditional check per class, rather than one per function. Cutting out inefficient code means we work faster. Apply this principle across a whole project rather than just one file, and you’ll see the benefits of it. We went through a few hurdles in order to make it here. Here’s what you need to look out for: How to Remove a WordPress Action from Another Class inside a Namespaced Class In the hook_wordpress method, run this: remove_action( 'tag_name', [ 'NAMESPACEOther_Class_Name', 'function_to_remove' ], identical_priority ); This was by far the hardest thing for me to figure out. The docs on remove_action() aren’t that great, and while they address how to remove an action from a class, they don’t address how to remove an action from namespaced class. Here are the requirements: The layout classes must be static. You have to match the tag, namespace, function name, and priority. So if you run add_action() with a priority of 20, remove_action() has to use 20 as well. Keeping Layout Classes Focused Across Multiple Post Types On a recent project, there were two post types: the default “post” post type, and a “news” post type. Each post type had a taxonomy too: “category” and “news-category.” We used just about the same layout for each post type and taxonomy. In order to prevent myself from duplicating code, here are the layout classes I created: NewsAndPost NewsAndPostArchive NewsAndPostSingular News NewsArchive NewsSingular Post PostArchive PostSingular If I didn’t have the “news-and-post-” files, then I would have had to duplicate the code that applied to both post types. If I only had the “news-and-post-” files, I would have had to split layout conditionals, i.e. if news-singular, fire these 3 functions, if post-singular, fire these 2, and so on. Create Custom Conditionals As Necessary A quick note on conditional usage: for the NewsAndPost class, I created a custom conditional called is_news_or_post() so that I wouldn’t have to write out the whole thing twice. <?php /** * is_news_or_post * * A custom boolean check for Hexadite so that we can test if we're on a news or post layout without repeating every single check every time we need to. * * @return bool */ function is_news_or_post() { $bool = false; if ( is_singular( 'post' ) ) { $bool = true; } if ( is_singular( 'news' ) ) { $bool = true; } if ( is_post_type_archive( 'post' ) ) { $bool = true; } if ( is_post_type_archive( 'news' ) ) { $bool = true; } if ( is_tax( 'news-category' ) ) { $bool = true; } if ( is_home() ) { $bool = true; } if ( is_category() ) { $bool = true; } if ( is_tag() ) { $bool = true; } return $bool; } For the other, more specific layout classes, I used the conditional that made sense: is_post_type_archive( 'news' ) || is_tax( 'news-category' ) for NewsArchive, is_singular( 'news' ) for NewsSingular, and so on. So it’s fine to use more than one conditional clause, but I would recommend against splitting conditionals. You want the layout class to stay as focused as possible. In Conclusion The combination of using a theme wrapper, custom actions and layout classes together helps me keep my codebase better structured and organized, even on large projects. The ability to turn different parts of the layout off and on by using add_action or remove_action has been especially helpful as well. If you’re having trouble wrangling a large number of Template Conditionals, I’d encourage you to give layout-first organization a try.