Starting the Popular Recipes Block
Introduction
In this section, we’ll set up a new block called “Popular Recipes.” This block will allow users to display a list of popular recipes based on user-defined criteria, such as cuisine type and the number of posts to show. We’ll also set up server-side rendering to ensure the block reflects any updates to the content.
Step 1: Create the Block Directory and Files
- Create a Directory: Name it
Popular Recipes
. - Add Files: Inside this directory, create the following files:
block.json
index.js
popular-recipes.php
Step 2: Set Up the block.json
File
In the block.json
file, add the following configuration:
jsonCopy code{
"name": "plugin/popular-recipes",
"title": "Popular Recipes",
"attributes": {
"title": {
"type": "string",
"default": "Popular Recipes"
},
"postCount": {
"type": "number",
"default": 5
},
"cuisines": {
"type": "array",
"default": []
}
},
"render": "plugin/popular_recipes_cb"
}
Step 3: Set Up the index.js
File
In the index.js
file, import the necessary components and create the block:
javascriptCopy codeimport { registerBlockType } from '@wordpress/blocks';
import { InspectorControls, PanelBody, TextControl, RangeControl } from '@wordpress/editor';
registerBlockType('plugin/popular-recipes', {
edit({ attributes, setAttributes }) {
return (
<>
<InspectorControls>
<PanelBody title="Settings">
<TextControl
label="Title"
value={attributes.title}
onChange={(title) => setAttributes({ title })}
/>
<RangeControl
label="Number of Posts"
value={attributes.postCount}
onChange={(postCount) => setAttributes({ postCount })}
min={1}
max={10}
/>
{/* Additional controls for cuisines can be added here */}
</PanelBody>
</InspectorControls>
<div className="single-post">{attributes.title}</div>
</>
);
},
save() {
return null; // Server-side rendering
});
Step 4: Register the Block in PHP
In your register-blocks.php
file, add the following code to register the new block:
phpCopy codefunction register_popular_recipes_block() {
register_block_type( __DIR__ . '/popular-recipes/block.json' );
}
add_action('init', 'register_popular_recipes_block');
Step 5: Create the Server-Side Rendering Callback
In the popular-recipes.php
file, define the server-side rendering function:
phpCopy codefunction popular_recipes_cb($attributes) {
// Output buffering to capture the HTML
ob_start();
// Example: Fetching posts and rendering HTML
$query_args = [
'post_type' => 'recipe',
'posts_per_page' => $attributes['postCount'],
// Add query args for cuisines if needed
];
$query = new WP_Query($query_args);
if ($query->have_posts()) {
echo '<div class="popular-recipes">';
while ($query->have_posts()) {
$query->the_post();
echo '<div class="single-post">';
echo '<h2>' . get_the_title() . '</h2>';
echo '<div>' . get_the_content() . '</div>';
echo '<p>By ' . get_the_author() . '</p>';
echo '</div>';
}
echo '</div>';
}
// Reset post data
wp_reset_postdata();
return ob_get_clean();
}
Enhancing the Popular Recipes Block with Query Controls
Introduction
We’ll enhance our Popular Recipes block by utilizing the Query Controls component from the WordPress components library. This component simplifies the process of modifying query parameters for fetching posts, such as limiting the number of posts and filtering by categories.
Step 1: Import the Query Controls Component
- Open the
index.js
file of your Popular Recipes block. - Import the Query Controls component:
javascriptCopy codeimport { QueryControls } from '@wordpress/components';
Step 2: Add Query Controls to the Sidebar
Next, locate the PanelBody
component within your index.js
file. Inside this component, add the QueryControls
component. Set up the properties as follows:
javascriptCopy code<PanelBody title="Query Settings">
<QueryControls
numberOfItems={attributes.postCount}
minItems={1}
maxItems={10}
onNumberOfItemsChange={(count) => setAttributes({ postCount: count })}
// Add additional properties here for categories
/>
</PanelBody>
Explanation of Properties
- numberOfItems: Sets the initial post count using the
postCount
attribute. - minItems and maxItems: Restrict the number of posts that can be displayed to between 1 and 10.
- onNumberOfItemsChange: Updates the
postCount
attribute whenever the user changes the slider.
Step 3: Adding Category Filtering
To allow filtering by categories, we will need to enhance the QueryControls
component. First, you’ll want to modify the attributes
in your block.json
file to include a categories
attribute:
jsonCopy code"attributes": {
"title": {
"type": "string",
"default": "Popular Recipes"
},
"postCount": {
"type": "number",
"default": 5
},
"cuisines": {
"type": "array",
"default": []
},
"categories": {
"type": "array",
"default": []
}
}
Updating the Query Controls for Categories
Now, within the QueryControls
, add properties to handle categories:
javascriptCopy code<QueryControls
numberOfItems={attributes.postCount}
minItems={1}
maxItems={10}
onNumberOfItemsChange={(count) => setAttributes({ postCount: count })}
// New properties for categories
categories={attributes.categories}
onCategoriesChange={(newCategories) => setAttributes({ categories: newCategories })}
/>
Explanation of New Properties
- categories: Binds the
categories
attribute to theQueryControls
so that it can reflect the selected categories. - onCategoriesChange: Updates the
categories
attribute when the user modifies the selected categories.
Step 4: Testing the Block
- Refresh the Gutenberg editor.
- Add the Popular Recipes block to the page.
- Check the sidebar to ensure the Query Controls component displays the slider for the number of posts and a section for category selection.
Adding Category Suggestions to the Popular Recipes Block
Introduction
We will enhance our Popular Recipes block by adding category suggestions for filtering recipes based on specific cuisines. This will allow users to display popular recipes tailored to their preferred cuisine.
Step 1: Import Required Function
First, we need to import the useSelect
function from the WordPress Data package to query the database for cuisines:
javascriptCopy codeimport { useSelect } from '@wordpress/data';
Step 2: Query for Cuisine Terms
Inside the edit
function of your index.js
, we will use the useSelect
function to retrieve all terms from the “cuisine” taxonomy. Create a variable called terms
:
javascriptCopy codeconst terms = useSelect((select) => {
return select('core').getEntityRecords('taxonomy', 'cuisine', { per_page: -1 }) || [];
}, []);
Explanation
getEntityRecords
: This function retrieves the terms from the specified taxonomy.per_page: -1
: This parameter ensures we get all available terms without any limit.
Step 3: Format the Retrieved Terms
Next, we need to convert the retrieved terms into the format required by the Query Controls component. Below the terms
variable, define another variable called suggestions
:
javascriptCopy codelet suggestions = {};
terms.forEach(term => {
suggestions[term.name] = term;
});
Optional Chaining
To prevent errors if terms
is null
, we can use optional chaining:
javascriptCopy codeterms?.forEach(term => {
suggestions[term.name] = term;
});
Step 4: Pass Suggestions to Query Controls
Now we can pass the suggestions
variable to the QueryControls
component. Add the following properties:
javascriptCopy code<QueryControls
// Other properties
categorySuggestions={suggestions}
onCategoryChange={(newTerms) => console.log(newTerms)}
/>
Explanation of New Properties
categorySuggestions
: This property is set to the formattedsuggestions
object. It enables the input field for cuisine suggestions.onCategoryChange
: This event handler logs the selected terms when users make changes.
Step 5: Testing the Component
- Refresh the Gutenberg editor.
- Add the Popular Recipes block to the page.
- Initially, the input for categories may not appear until the terms are fetched.
- Once the request is completed, you should see the input field.
- Start typing in the input; existing cuisines should be suggested based on user input.
Updating the Cuisines Attribute in the Popular Recipes Block
Introduction
We’ll enhance our Popular Recipes block by updating the cuisines
attribute. This attribute will store an array of objects, where each object represents a term with its corresponding ID. This is essential for filtering recipes based on cuisine when querying posts.
Step 1: Modify the onCategoryChange
Event
In your index.js
file, scroll to the onCategoryChange
event in the Query Controls component. Replace the console statement with the following code:
javascriptCopy codeconst newCuisines = [];
Explanation
newCuisines
: This variable will hold the new array of cuisine objects that will replace the currentcuisines
attribute.
Step 2: Loop Through the New Terms
Next, loop through the newTerms
argument with the forEach
function:
javascriptCopy codenewTerms.forEach(cuisine => {
let cuisineTerm;
if (typeof cuisine === 'object') {
// If it's an existing term object, push it directly.
newCuisines.push(cuisine);
return; // Early exit for this iteration
}
// Otherwise, it's a string, so we need to find the term.
cuisineTerm = terms.find(term => term.name === cuisine);
if (cuisineTerm) {
newCuisines.push(cuisineTerm); // Push the found term object
}
});
Explanation
- Type Check: The first condition checks if
cuisine
is an object (representing an existing term). If it is, we push it directly into thenewCuisines
array and exit early from the loop. - Finding Terms: If
cuisine
is a string, we search for it in theterms
array and, if found, push the corresponding term object into thenewCuisines
array.
Step 3: Update the Cuisines Attribute
After looping through all terms, we can update the cuisines
attribute with the new array:
javascriptCopy codesetAttributes({ cuisines: newCuisines });
Step 4: Add Initial Values to the Query Controls
Before testing, make sure to set the selectedCategories
property in the Query Controls component. This allows the component to reflect the current selections:
javascriptCopy code<QueryControls
// Other properties...
categorySuggestions={suggestions}
onCategoryChange={onCategoryChange}
selectedCategories={attributes.cuisines.map(term => term.name)} // Add this line
/>
Explanation
selectedCategories
: This property maps thecuisines
attribute to an array of term names, ensuring the Query Controls reflect the current selections.
Step 5: Testing the Code
- Refresh the page in the Gutenberg editor.
- Add the Popular Recipes block to the page.
- Try adding cuisines through the input field. You should be able to add and remove cuisines seamlessly.
- Check the console to ensure the attribute is being updated correctly.
Testing the Recipe Endpoint with Postman
Introduction
We’ll test the custom endpoint for our recipes using Postman. We will ensure that we can retrieve our created recipes in the correct order based on their ratings.
Step 1: Create Test Recipes
Before testing, ensure you have created three recipes:
- Recipe One: 5 stars
- Recipe Two: 2 stars
- Recipe Three: 4 stars
Step 2: Set Up Postman
- Open Postman and create a new request.
- Set the request type to GET.
- In the URL field, enter:bashCopy code
http://udemy.local/wp-json/wp/v2/recipe
Step 3: Add Query Parameters
We want to filter the results:
- Under the Params tab, add the following parameters:
- per_page:
10
- cuisine:
[insert_cuisine_term_id_here]
(replace this with the actual term ID for the cuisine) - _embed: (add this parameter without a value)
- per_page:
How to Find the Cuisine Term ID
- Go to the WordPress Admin dashboard.
- Navigate to Cuisines under the Taxonomies section.
- Click on the cuisine associated with your recipes.
- In the URL, find the
tag_ID
parameter to get the term ID.
Step 4: Send the Request
- Click Send to execute the request.
- Check the response body. You should see an array of recipes.
Expected Output
- The response should include three recipes.
- Each recipe should have properties like title, author, and _embedded which contains details about the author and WP featured image.
Step 5: Validate Response Order
Check the order of recipes in the response. They should ideally be:
- Recipe One (5 stars)
- Recipe Three (4 stars)
- Recipe Two (2 stars)
Problem Identification
If the order is incorrect (e.g., Recipe Three comes first), we need to address how the posts are being sorted. By default, WordPress does not support sorting by custom metadata (like ratings).
Step 6: Modifying the Query for Sorting
To sort by rating, we will need to implement a filter to adjust the query parameters before they are sent to the database.
Modifying the Recipe Query for Sorting by Rating
Introduction
We will modify the REST API query to ensure that recipes are sorted by their rating. We’ll achieve this by using a filter hook that allows us to adjust the query parameters before they are sent to the database.
Step 1: Set Up the Filter Hook
- Open the Main Plugin File: Navigate to your main plugin file.
- Add the Filter Hook: At the bottom of the hook section, add the following line:phpCopy code
add_filter('rest_recipe_query', 'up_rest_recipe_query', 10, 2);
- The hook name uses a dynamic placeholder for the post type.
- The function name will be
up_rest_recipe_query
.
Step 2: Define the Query Modification Function
- Create a New File: Inside the
includes/rest-api
folder, create a file namedrecipe-query-mod.php
. - Define the Function:phpCopy code
function up_rest_recipe_query($args, $request) { // Check for 'order_by_rating' parameter if (isset($request['order_by_rating'])) { $args['orderby'] = 'meta_value_num'; $args['meta_key'] = 'recipe_rating'; // Key for sorting by rating } return $args; }
- Here,
$args
contains the query settings. - We check if the
order_by_rating
parameter is present in the request. If it is, we modify the query to order by the recipe rating.
- Here,
Step 3: Update the Postman Request
- Open Postman: Go back to your Postman setup from the previous lecture.
- Modify the Request:
- Add the following parameters to your request:
- order_by_rating: (this parameter does not need a value, just include it)
- order:
descending
- Your complete query parameters should look like this:
- per_page:
10
- cuisine:
[insert_cuisine_term_id_here]
- _embed: (leave blank)
- order_by_rating: (include this)
- order:
descending
- per_page:
- Add the following parameters to your request:
Step 4: Send the Modified Request
- Click Send: Execute the request in Postman.
- Check the Response: Look at the response body. The order of the recipes should now be:
- Recipe One (5 stars)
- Recipe Three (4 stars)
- Recipe Two (2 stars)
Updating the Block to Perform a Request for Posts
Introduction
We will modify our block to request posts from the REST API based on the selected cuisine terms. This request will be similar to what we previously did in Postman, with a few necessary adjustments.
Step 1: Extract Cuisine IDs
- Open the
index.js
File: Start by opening your main JavaScript file for the block. - Create a Variable for Cuisine IDs:javascriptCopy code
const cuisineIds = cuisines.map(term => term.id);
- This line creates an array of IDs from the
cuisines
attribute, which contains the term objects. Themap
function extracts theid
property from each term.
- This line creates an array of IDs from the
Step 2: Initiate the Request for Posts
- Define a Variable for Posts:javascriptCopy code
const posts = useSelect((select) => { return select('core').getEntityRecords('postType', 'recipe', { per_page: count, _embed: true, cuisine: cuisineIds, order: 'desc', orderby: 'rating', }); }, [count, cuisineIds]);
- Here, we use
useSelect
to fetch the posts. ThegetEntityRecords
function retrieves posts of therecipe
post type. - The parameters passed to
getEntityRecords
include:- per_page: Set to the variable
count
. - _embed: Set to
true
to include embedded data (like featured images and authors). - cuisine: Set to the
cuisineIds
array. - order: Set to
desc
for descending order. - orderby: Set to
rating
to sort by recipe ratings.
- per_page: Set to the variable
- Here, we use
Step 3: Watch for Changes
- Set Up Dependencies:javascriptCopy code
}, [count, cuisineIds]);
- This ensures that if the user changes the
count
orcuisineIds
, the request will be sent again, providing fresh results.
- This ensures that if the user changes the
Step 4: Log the Posts Data
- Log the Posts:javascriptCopy code
console.log(posts);
- This will help us verify that the data is being fetched correctly.
Step 5: Testing the Request
- Refresh the Gutenberg Editor: Open the Gutenberg editor to see your changes.
- Add the Popular Recipes Block: Insert the block into your post or page.
- Open Developer Tools: Navigate to the console panel.
- Initially, the log will show
null
, indicating that the data is not yet available. - After a few moments, you should see an array logged in the console with the post data.
- Initially, the log will show
Rendering the Template for Our Block
Introduction
In this lecture, we will render the template for our block to display posts. The data we need is already available, so we’ll loop through it to create the necessary HTML structure.
Step 1: Open the index.js
File
- Access the File: Start by opening the
index.js
file in your code editor.
Step 2: Create a Loop for Rendering Posts
- Scroll to the
Single Post
Class: Locate the class that contains the template for displaying a single post. - Add a Loop:javascriptCopy code
{posts ? posts.map((post) => { return ( <div className="single-post"> {/* Template for rendering each post */} </div> ); }) : null}
- This loop will iterate over the
posts
array, returning a template for eachpost
.
- This loop will iterate over the
Step 3: Check for Featured Image
- Define a Variable for the Featured Image:javascriptCopy code
const featuredImage = post._embedded && post._embedded['wp:featuredmedia'] && post._embedded['wp:featuredmedia'].length > 0 ? post._embedded['wp:featuredmedia'][0] : null;
- This line checks if the post has an embedded featured image and assigns it to the
featuredImage
variable.
- This line checks if the post has an embedded featured image and assigns it to the
Step 4: Render the Featured Image
- Conditionally Render the Image:javascriptCopy code
{featuredImage && ( <a href={post.link} className="single-post-image"> <img src={featuredImage.media_details.sizes.thumbnail.source_url} alt={featuredImage.alt_text} /> </a> )}
- This checks if the
featuredImage
exists before rendering the image and anchor tag.
- This checks if the
Step 5: Render Post Title and Author
- Add Title and Author Below the Image:javascriptCopy code
<a href={post.link}> <RawHTML>{post.title.rendered}</RawHTML> </a> <p>{post._embedded.author[0].name}</p>
- This displays the title and author below the featured image. The title is wrapped in a
RawHTML
component to prevent HTML escaping.
- This displays the title and author below the featured image. The title is wrapped in a
Step 6: Testing the Block
- Refresh the Page: Go to the Gutenberg editor and refresh.
- Add the Popular Recipes Block: Insert the block into your post or page.
- Observe the Behavior: Initially, the list of posts will be empty as the request is being processed. After a few moments, the posts should appear.
Step 7: Verify Changes
- You can modify the block settings (like filtering by a taxonomy term) to see if the list updates correctly. This ensures that our block works as intended.
Step 8: Prepare for Server-Side Rendering
- Before moving on, ensure you save your post with the block changes. We’ll focus on rendering the block on the front end with PHP in the next lecture.
Finishing the Popular Recipes Block
Introduction
Let’s begin working on finishing the popular recipes block by rendering it with the server.
Initial Steps
We’ve taken the first steps of accomplishing this task by defining a function for rendering the block in your editor.
File Access
Open the popular recipes dot PHP file.
Grabbing Attributes
First things first.
Accepting Attributes
Let’s grab the attributes by accepting the attributes argument.
Extracting the Title
Next, we can begin extracting the attributes, starting with the title.
- Create a variable called title.
- The value for this variable will be the escape html function with the attributes title variable.
- As usual, we are escaping the title before rendering it on the screen.
- This step is to reduce the likelihood of malicious HTML being inserted into the document.
Grabbing Cuisine IDs
Next, let’s grab the cuisine IDs.
- Create a variable called cuisine IDs.
- The value for this variable should be an array of cuisine IDs in our attributes.
- We’re storing an array of objects where each object represents a term from the cuisine taxonomy.
Creating an Array of IDs
Let’s create a new array where each item is the ID.
- Let’s set this variable to the return value of the array map function.
- This function is defined by the PHP language.
- The array map function will loop through an array.
- On each iteration, we can return a new value to replace the current value of the item.
- For our purpose, we’re trying to grab the ID.
Setting Up the Array Map Function
There are two arguments which is a function for handling each iteration of the loop and the array.
- Let’s pass in an anonymous PHP function.
- Next, pass in the attributes cuisine’s variable.
Function Definition
The function passed into the array map function will be given each item in the array.
- Let’s reference each item as an argument called term.
- Within the function.
- Let’s return the term ID item.
Object to Array Conversion
Technically, the items in the array are objects.
- However, WordPress will convert the JavaScript objects into PHP arrays.
- The property name will be converted into an array key.
- We can reference a specific item from the object by its name as an array key.
- The value returned by this function will be a new array of term IDs.
Initiating the Query for Posts
We can finally begin initiating the query for posts.
Creating the Query
Previously, WordPress would create the query for us.
- We did not have to create the query ourselves with the rest API.
- This time we must manually create the query with the WP query class.
Defining the Query Variable
After the variables define a variable called query, the value for this variable will be a new instance of the WP query class.
- New instances of a class can be configured by passing data inside the parentheses.
- We haven’t had the opportunity to discuss this feature.
- However, the PHP classes can support arguments similar to functions by default.
Querying the Database
WordPress will query the database for a list of posts for our block.
- We’re not interested in posts, so let’s modify the query by passing in an array above the query variable.
- Define a variable called arguments with an initial value of an array.
- The query can be modified with an array.
- In this array we can add parameters documented on the page for the WP query class.
- Most of these parameters are going to be familiar to us.
Setting Query Parameters
Filtering by Post Type
First, let’s filter the posts by the recipe post type by setting the post type key to recipe.
Limiting Number of Posts
Next, we can limit the number of posts to retrieve by setting the posts per page key.
- The value for this key will be the attributes count variable.
- After limiting the results, we can start sorting the results by the rating of the recipe.
- Set the order by parameter to meta value num.
- Next, set the meta key parameter to recipe rating.
- Lastly, set the order key to descending.
Finalizing the Query
The query is almost ready.
- There’s one piece of information to add to the query.
- The results should be filtered by a specific taxonomy term.
Conditional Statement for Cuisine IDs
If the block has cuisine IDs, the query must be altered to filter the results below.
- The arguments variable create a conditional statement with the following condition not empty cuisine IDs.
- Before altering the query.
- We’re checking the cuisine IDs variable.
- It’s possible that the user may not want to filter their results by a specific cuisine.
- If that’s the case, the query should not be altered.
Modifying the Query
If the array contains at least one value, we can begin modifying the query in the conditional block.
- Let’s add a new key called tax query.
- A new item can be pushed into an array with this syntax.
- We do not need a function to push items into an array.
- PHP is very flexible with its arrays.
Taxonomy Filtering
The value for this new item will be an array within this array.
- We can add more arrays with configuration settings in each child array.
- We can filter the results by a specific taxonomy.
- As you know, a post type can have multiple taxonomies.
- The query can be refined for multiple taxonomies.
- However, in our case, we’re interested in filtering the results by the cuisine taxonomy.
Adding Taxonomy Parameters
Inside this array, let’s add a parameter called taxonomy.
- Set this parameter to cuisine.
- Next, we need to specify the field to filter the recipes by taxonomy.
- Terms can be sorted by their name, slug or ID.
- We only have their ids, so let’s set the field parameter to term ID.
Adding Terms Parameter
As a reminder, the information for these parameters can be found on the WP Query Class Documentation page.
- Next, let’s add a parameter called terms.
- The value for this parameter will be the cuisine IDs variable.
Summary of the Query
Let me break down the query we’ve created.
- Starting from the Loop, we’re performing a query for recipe posts.
- The number of results should not exceed ten posts.
- The order of the recipes will be sorted by their ratings.
- The highest rated recipe will appear at the top of the array.
- If the posts should be filtered by a specific taxonomy term, we are adding to the query by adding the tax query parameter.
- In this parameter, we are filtering the posts further by a set of terms within the cuisine taxonomy.
Finalizing the Arguments
Overall, we have a query for grabbing the most highly rated recipes on our site.
- Let’s pass on the arguments variable to the new instance of the WP query class.
- After the instance has been created, WordPress will initiate a query to the database.
- This query will return a list of posts which are stored in the query variable.
Looping Through Query Results
Introduction
We are going to begin looping through the results in the query variable before writing a single line of code.
Understanding the Loop
I want to quickly mention a concept called the Loop. WordPress has a term to refer to what we’re doing in our block. The idea of querying the database for posts and looping through the results is known as the Loop. This concept is mainly for theme developers. However, it can apply to plugin developers too.
Documentation Reference
This documentation page will describe the proper method for querying the database and looping through the results. Feel free to read through this page for more info.
Getting Started
We are going to be adhering to the process recommended on this page.
Opening the File
Let’s get started in your editor. Open the popular recipes dot PHP file.
Writing the Template
Inside the function, we will begin writing the template after the OB start function.
Wrapping the Block
The block will be wrapped with a div tag. This tag will have a class called WP BLOCK Utility plus popular recipes.
Rendering the Title
Next, we can render the title by adding a pair of H six tags. Inside these tags, let’s echo the title variable.
Displaying the Posts
Afterward, we can begin displaying the posts for this task. We need to go back into PHP mode inside a pair of PHP tags.
Adding Conditional Statement
Add a conditional statement. On the query object, there’s a method called have posts. Let’s call this function from within the conditional statement.
Checking for Results
Before looping through the results, we should check the query for results. It’s possible that WordPress may not have been able to find recipes. In this case, we should not bother looping through the results. This function will return a boolean value. If there are results, the value will be true.
Beginning the Loop
Inside this conditional block, we can begin looping through the data with a while loop. Inside the while loop, call the have posts function again.
Loop Continuation
The have posts function performs another action. It’ll check if the posts have been looped through. If every post has been looped through, this function will return false, thus causing the loop to end.
Moving to the Next Post
At the moment, the loop will run infinitely. We are not moving on to the next post in the while block. Run a function called the post. The post function will check if the loop is running for the first time; if it is, the first post is grabbed from the results, otherwise it will move on to the next post in the list of results.
Importance of the Post Function
It’s always important to call this function at the beginning of the loop. Otherwise, you may experience inconsistent results.
Displaying Post Data
Now that we have our data, we can begin displaying the template for each post.
Adding Single Post Wrapper
First, let’s exit out of PHP mode. Next, add a div tag with a class called Single Post.
Adding Anchor Element
Afterward, add an anchor element with a class called Single Post Image. On this element, let’s set the href attribute to a function called the permalink.
Using Template Tags
The permalink function is a template tag. We talked about template tags before. There are functions for rendering specific pieces of content. There are a series of template tags that can run inside a loop. These functions will be able to detect their inside a loop.
Benefits of Using the Loop
If a template tag detects a loop, it’ll attempt to grab the post in the current iteration. This is the main benefit of using the loop. WordPress has designed its functions for handling most of the work. You do not need to provide the ID of the post or any other piece of information. It’s capable of doing the heavy lifting as long as we’re calling the post function on each iteration. Everything else is handled for us. This function will return the URL to a specific post.
Rendering the Thumbnail
Let’s render the image from within this element. Inside the anchor element, run a function called the thumbnail. The thumbnail function will generate an image tag with the source pointing to the image of the post. This function has an optional parameter which is the size of the image. Let’s set the size to thumbnail.
Rendering Title and Author
We’ve successfully rendered the thumbnail. Let’s proceed to render the title of the post and the author below.
Adding Single Post Detail
The anchor element added div tag with a class called Single Post Detail. Next, add an anchor element with the href attribute. The value for this attribute will be the permalink function. Inside this tag, we are going to render the title. The title can be rendered by calling the title function.
Author Information
For your convenience, I provided a link to a page with a complete list of template tags in WordPress. Feel free to check it out. Most of these functions will handle grabbing the information for us.
Rendering Author Name
Next, let’s render the name of the author below. The anchor element will add a span tag. Inside this tag, add the word “by,” followed by an anchor element. One more time, set the href attribute to the permalink function. Inside the element, we are going to render the name of the author. The author can be displayed by calling the author function.
Final Steps
The template is ready. There’s one more crucial step below the loop.
Resetting Post Data
Let’s run a function called WP Reset Post Data. We’ve talked about this before, but I want to mention it again.
Importance of Resetting
Regardless of what page we’re viewing, WordPress will always perform a query to the database for a list of posts. This query is considered the main query; queries written by a plugin or theme are considered secondary queries.
Handling Query Focus
Whenever we create a custom query, WordPress will shift its focus from the main query to our secondary query. This shift in focus allows us to use template tags without worrying about informing WordPress of the current post.
Consequences of Custom Queries
This behavior does have consequences. If another developer performs the loop, the data from the loop will contain the data from our query. That’s not what we want. It’s likely that other developers will want data from the main query.
Finalizing the Query
By calling this function, we’re telling WordPress that we’re finished with the secondary query. It can go back to focusing on the main query.
Creating the Recipe of the Day Block
Introduction
We are going to create a block for displaying the recipe of the day. Every day we’re going to randomly select a recipe from the current site. This recipe will be displayed to our visitors like the other blocks.
Resources
I prepared this block for us in the resource section of this lecture. I provide a link to the starter files in our plugin. Create a directory called Daily Recipe inside this directory and these files. Pause the video and good luck.
Setting Up the Starter Files
Welcome back. I’ve created the starter files in my plugin. Let’s go through the files together.
Reviewing the Block JSON File
Starting with the block JSON file, we have a basic configuration file. This block is going to have a single attribute, which is an attribute for storing the title of the block.
Moving to the JavaScript File
Next, let’s move over to the JavaScript file. Inside this file, we have a basic block registration. Inside the template, we’re rendering a rich text component for modifying the block’s title. Below this component, we are rendering a post image and title.
API Request
As you can imagine, we are going to be sending a request for a random post from the REST API. There’s not much else to say about the files.
Registering the Block
Let’s go ahead with registering this block. Open the register block start PHP file.
Modifying the Blocks Array
Inside the blocks array, add another array for the daily recipe block. Let’s add the name option. Next, add the options key.
Server Side Rendering
Querying the database for a random recipe is going to require server-side rendering. Let’s set the render callback option to up daily recipe CB.
Creating the PHP File
Next, create a file called daily recipe dot PHP inside the include slash blocks directory. Define the up daily recipe CB function. Lastly, insert the output buffer snippet.
Writing the Query for a Random Recipe
Introduction
We are going to begin writing the query for grabbing a random recipe before we get into writing code. There’s an important concept we should cover.
Understanding Transients
WordPress has a feature called Transients. In the resource section of this lecture, I provide a link to the documentation page for this feature. Up until now, data stored in a database has been permanent. It’s possible to delete data, but it’s not common. Storing data in a database is a great way to persist data.
Temporary Data Storage
However, what if we want to store data temporarily? We could use variables, but a variable is cleared from a system’s memory after PHP is finished running. Transients are the solution for storing data for longer than a few seconds. A transient is a value stored in the database. The record will be deleted after a specific expiration time.
Benefits of Transients
Best of all, WordPress manages this process for us. We never need to worry about deleting the record from the database. WordPress automates the deleting of a record past an expiration time.
Why Use Transients?
The question becomes: Why do we need transients? Our block will display the recipe of the day. Every day we’re going to refresh the recipe. The ID of the current recipe will be stored in a database for 24 hours. After 24 hours, the ID should be deleted. A new ID will take its place. Transients are the perfect solution for handling this scenario.
Storing Recipe IDs
We are going to store the ID of the recipe with the Transients API. If WordPress deletes the value from the database, we can safely query the database again for another recipe. It’s a great API for caching data.
Creating the Generate Daily Recipe Function
Let’s get started by heading over to our editor. In the includes directory, create a file called Generate Daily Recipe PHP.
Defining the Function
Inside this file, define a function called up generate daily recipe. This function will be responsible for querying the database for a recipe.
Custom SQL Query
WordPress does not have a function for generating a randomized value. Since that’s the case, we will be writing a custom SQL query inside the function. Let’s grab the WP DB global variable.
Querying the Database
Next, define a variable called ID. The value for this variable will be the wp db get var function. The goal of this query is to grab a single value. We’re not interested in the entire record of a post. The VAR function is our best option for retrieving a single value from a query.
Hard-Coding the Query
Unlike before, we’re not going to prepare the query. Preparing the query is useful for sanitizing a query that has user input. For this query, the query will not be created with user input. It’s going to be a hard-coded query inside the get var function.
Writing the SQL Query
Type the following query:
sqlCopy codeSELECT ID FROM WP db posts
The query we’re building will select the ID column from the post table. We’re not hard-coding the name of the table. WordPress has a property for the WP post table. I prefer dynamically inserting the name over hard coding the name. The name of the table may change; by relying on WordPress to set the name, we can safely avoid typos.
Adding Conditions to the Query
Next, let’s add the following to the query:
sqlCopy codeWHERE post_status = 'publish' AND post_type = 'recipe'
The results will be filtered by two conditions. Not all posts are published immediately. Some authors may want to create drafts of a recipe. We’re not interested in drafts. The posts table can store drafted and published posts. In this query, we’re limiting the results to published posts. The second condition will limit the posts further to the recipe post type.
Finalizing the Query
All post types are stored in the post table. This includes custom post types. Once again, we’re not interested in another post type besides recipes. After these conditions, let’s finalize the query with the following code:
sqlCopy codeORDER BY RANDOM LIMIT 1
By default, the database will order the results in descending order. Our goal is to grab a random recipe. SQL has a function called RANDOM for randomizing the records in a database. This will not affect the database directly; it will only affect the results.
Storing the ID
We can sort the ordering by using the ORDER BY keywords. The last keyword is called LIMIT. The LIMIT keyword will limit the results returned by the query. After this keyword, we can specify the limit. Overall, this query will retrieve the ID from a random record in the post table.
Using Transients API
Let’s store the ID in our database. After the ID variable, run a function called set transient. The set transient function will store the value in the database with an expiration date. The first argument of this function is the custom name of the transient. Let’s set the name to up daily recipe ID.
Setting Expiration
The second argument is the value passed in the ID variable. Lastly, we must provide the expiration in seconds. Let’s head back to the documentation page for the Transients API. On the sidebar, there is a link called Using Time Constraints. Click on it.
Choosing Expiration Constants
This link will take you to a section with a list of constraints defined by WordPress. We can manually provide a time. On the other hand, WordPress has constants for the most common expiration times. I prefer to use these constants. Let’s grab the day in seconds constant.
Finalizing the Function
Next, let’s update the set transient function by passing in this constant as the value for the third argument. Lastly, let’s return the ID from the function. This function is not tied to a specific hook or request. It’s a plain PHP function.
Purpose of the Function
The reason is simple. It’s because we’ll be performing the action of grabbing a recipe from different locations in our app to prevent our codebase from becoming repetitive. We’ve outsourced the logic into a separate function.
Creating a Custom REST API Endpoint for a Random Recipe
Introduction
We are going to create a custom REST API endpoint for grabbing a random recipe. We’ll be using our function to help us generate this information.
Setting Up the Endpoint
First, let’s register a new route in your editor. Open the init.php
file.
Registering the Route
At the bottom of the function, add a new route with the register_rest_route
function.
Setting the Namespace and Endpoint
Set the namespace to up/v1
and the endpoint to daily-recipe
.
Configuring the Route
Next, pass in an array to configure the settings of our new route:
- Methods: Set the methods option to the
WP_REST_Server::READABLE
constant. This will set the method of the route to GET, which makes sense since we aren’t allowing users to create new daily recipes. The endpoint will always return a single recipe. - Callback: Set the callback option to
up_rest_api_daily_recipe_handler
. - Permission Callback: Set the permission callback option to a simple function that returns
true
. This means the endpoint will be accessible to all users without special permissions.
Completing the Route Registration
Now that our route has been registered, it’s time to handle the request by defining the function.
Defining the Handler Function
Inside the includes/rest-api
directory, create a file called daily-recipe.php
.
Writing the Function
Inside this file, define the up_rest_api_daily_recipe_handler
function. Typically, we would send a response with a key called status
. However, this endpoint will return post data and not contain a status.
Creating the Response Array
Create an array called response
. Inside this array, we’ll store the URL, image, and title of a post. This information is sufficient for displaying the recipe of the day.
Retrieving the Transient
Define a variable called ID
. The value for this variable will be the get_transient
function, which will return a transient value from the database. The name of the transient must be passed as the first argument, which in our case is up_daily_recipe_id
.
Checking for a Valid ID
If WordPress is unable to grab a value, it will return false
. To handle this, let’s verify the contents of the variable with a conditional statement. Check if the ID
variable is false
. If it is, we should generate a new transient by calling the up_generate_daily_recipe
function.
Updating the Response
At this point, we are guaranteed to have a post ID. The last step is to update the response with the appropriate values:
- Response URL: Set the response URL variable to the
get_permalink
function with theID
variable. - Response Image: Set the response image variable to the
get_the_post_thumbnail_url
function with theID
variable. Passfull
as the second argument to get the original image size. - Response Title: Set the response title variable to the
get_the_title
function with theID
variable.
Returning the Response
After updating the response, return it from the function.
Testing the Endpoint
Let’s test the endpoint using Postman. Postman should have saved your previous session. Modify the current URL to our new endpoint:
bashCopy codeup/v1/daily-recipe
Sending the Request
After making those changes, send the request. In the request body, you should receive a random recipe from the posts.
Verifying the Behavior
Try sending the request again. The same post should appear in the body. A new recipe will not be selected until 24 hours have passed, which is the exact behavior we were looking for.
Displaying the Recipe in the Gutenberg Block
Introduction
We are going to begin displaying the recipe in the block for the Gutenberg editor. We have already prepared the endpoint for grabbing this information.
Setting Up the Block
Let’s start by sending a request to our custom endpoint from our block. Open the index.js
file in your editor.
Importing Required Components
At the top of the file, we need to import some components and functions:
- API Fetch: Import the
fetch
function from@wordpress/api-fetch
. This function will help us send a request to our REST API. - React Hooks: Import
useState
anduseEffect
from@wordpress/element
.- The
useEffect
function will help us send a request when the component initializes. - The
useState
function will be used to store the post data in the component state.
- The
- Spinner Component: Import the
Spinner
component from@wordpress/components
. This will indicate that the post is loading while the request is being processed.
Initializing Component State
Scroll to the edit
function and define the component state using useState
.
javascriptCopy codeconst [post, setPost] = useState({
isLoading: true,
url: null,
image: null,
title: null,
});
- The
isLoading
property is set totrue
initially, showing the spinner while the request is pending. - The other properties (
url
,image
, andtitle
) will be updated with the data from the response.
Sending the Request
Next, let’s use useEffect
to send the request.
- Call
useEffect
and pass an arrow function as the first argument. Use theasync
keyword before the function definition to handle the asynchronous request. - Pass an empty array as the second argument to ensure the function runs only once during the component’s initialization.
Inside the effect, send the request:
javascriptCopy codeconst fetchData = async () => {
const response = await apiFetch({ path: '/up/v1/daily-recipe' });
setPost({
isLoading: false,
...response,
});
};
fetchData();
- Here, we use
apiFetch
to send a request to our custom endpoint. - Once we receive the response, we update the state with
setPost
, settingisLoading
tofalse
and spreading the response data into the state.
Updating the Template
Now, let’s update the block’s template to display the recipe data.
Rendering the Spinner and Recipe
Scroll to the template and update the return statement:
javascriptCopy codereturn (
<>
{post.isLoading ? (
<Spinner />
) : (
<a href={post.url}>
<img src={post.image} alt={post.title} />
<h3>{post.title}</h3>
</a>
)}
</>
);
- Use a ternary operator to check
post.isLoading
. If it’strue
, render theSpinner
component. Iffalse
, render the anchor element with the recipe data.
Setting Attributes Dynamically
Within the anchor element:
- Set the
href
attribute topost.url
. - Set the
src
attribute of theimg
element topost.image
. - Set the inner text of the
h3
element topost.title
.
Testing the Block
Let’s test the block before moving on.
- Refresh the Gutenberg editor.
- Add the daily recipe block to the page.
Initially, the spinner should appear for a brief moment. Once the request is complete, the post data will be displayed in the block.
Finalizing the Daily Recipe Block
Introduction
We are going to finish our block by rendering it with our PHP function. Many of these steps will be familiar to you, so we’ll move through them quickly.
Updating the PHP Function
Open the daily-recipe.php
file. This file was created to define the function for rendering the block on the server.
Adding the Attributes Argument
- Add Attributes Argument: Update the function to accept an
$attributes
argument. - Extract Attributes: Since there is only one attribute (the title), define a variable called
$title
and set its value using theesc_html()
function:phpCopy code$title = esc_html($attributes['title']);
Getting the Recipe ID
Next, grab the recipe ID:
- Define ID Variable: Create a variable called
$ID
and set its value using theget_transient()
function with the transient nameup_daily_recipe_ID
. - Check for Empty Value: Create a conditional statement to check if
$ID
is empty. If it is, call theup_generate_daily_recipe()
function to get a new recipe ID.phpCopy codeif (empty($ID)) { $ID = up_generate_daily_recipe(); }
Rendering the Output
Now, we can render the information in the output buffer:
- Create the Main Container: Add a
<div>
tag with a class ofwp-block-udemy-daily-recipe
:phpCopy codeecho '<div class="wp-block-udemy-daily-recipe">';
- Render the Title: Inside this div, add an
<h6>
tag and echo the$title
variable:phpCopy codeecho '<h6>' . $title . '</h6>';
- Create the Anchor Element: Below the
<h6>
tag, add an anchor element with thehref
attribute set to the permalink of the post:phpCopy codeecho '<a href="' . get_permalink($ID) . '">';
- Render the Image: Use the
get_the_post_thumbnail_url()
function to retrieve the post’s thumbnail URL. Set this as thesrc
attribute of an<img>
element:phpCopy codeecho '<img src="' . get_the_post_thumbnail_url($ID, 'full') . '" alt="' . esc_attr(get_the_title($ID)) . '" />';
- Render the Post Title: Finally, add another pair of
<h3>
tags to echo the post title:phpCopy codeecho '<h3>' . get_the_title($ID) . '</h3>'; echo '</a>'; // Close the anchor tag echo '</div>'; // Close the main container
Testing the Block
Once you’ve completed the updates, it’s time to test the code. Refresh the page where the daily recipe block is used in the Gutenberg editor.
Understanding Performance in WordPress
Importance of Performance
Performance is crucial for any website. A slow-loading site can lead to user frustration and increased bounce rates, which we definitely want to avoid—especially with our daily recipe block.
The SQL Query
We implemented a custom SQL query for retrieving a random recipe, which is efficient but worth discussing further.
WordPress vs. Custom Queries
I want to clarify something: while WordPress has its built-in solutions for querying the database, these can sometimes be slower than a custom query.
Measuring Performance
To measure the performance of our code, we can utilize the microtime()
function in PHP. This function captures the current time in microseconds, allowing us to determine how long a specific line of code takes to execute.
Example of Performance Measurement
- Capture Start Time: At the start of our
up_generate_daily_recipe()
function, we usemicrotime(true)
to capture the current time.phpCopy code$start_time = microtime(true);
- Execute the SQL Query: After storing the start time, we run our SQL query to fetch a random recipe.
- Capture End Time: Immediately after the query completes, we again call
microtime(true)
to get the end time.phpCopy code$end_time = microtime(true);
- Calculate Execution Time: By subtracting the start time from the end time, we can find out how long the SQL query took to execute.phpCopy code
$sql_execution_time = $end_time - $start_time;
- Using WP_Query: In contrast, we also recreate the same query using the
WP_Query
class and follow the same steps to capture its execution time.
Results
After adding these measurements to our block, refreshing the page shows the execution times for both queries. Typically, you will find that the custom SQL query runs faster than the WP_Query
method.
Key Takeaways
- Custom SQL Queries: While they can be faster in specific scenarios, always consider the trade-offs in safety and maintainability.
- Performance Measurement: Using
microtime()
allows you to easily measure and compare the performance of different approaches. - Choosing the Right Approach: While raw SQL queries can be more efficient, it’s often safer and easier to use WordPress’s built-in methods unless performance becomes a critical issue.