Last week I published a tutorial on building a “post of the day” block that will randomly pick a post to display and automatically switch it up every 24 hours.
This week, we’re going to add some extra functionality to this block by adding an option in the Gutenberg editor to allow us to pick how many random posts we want to feature. This way you could have a grid of three to four featured posts that rotate out daily if you wanted.
Using the concepts from this week’s write up you could also add more controls to limit to a specific taxonomy term, customize the order of the posts, etc!
If you haven’t read part one, I suggest starting with that article, since this builds on where we left off.
We’ll start with our finished block from last time – if you don’t have the files you can grab the finished part 1 state here (it is the main branch).
Outline
The basic outline of what we need to do is the following:
- Add a block attribute called count to keep track of how many posts to display
- Add a control for this attribute in our block
- Pass the attribute into our custom REST endpoint
- Update our transient to save an array of posts, not just one
- Update our block’s javascript file to accept an array of posts
- Update our block’s render template to display an array of posts
Let’s jump into it!
Adding a Block Attribute
If you’ve worked with blocks much before, this should be pretty straight forward. We’ll just jump into our block.json file and add a number attribute to keep track of our desired post count. We’ll set the default to 1, since this is what the block currently does.
// ... rest of block.json
"attributes": {
"count": {
"type": "number",
"default": 1
}
},
Add Inspector Controls
Now let’s add a control to our block. For this use case, I think a range slider makes sense we’d want a minimum of 1 post and reasonable maximum as well like 10 posts.
First, let’s import a few things from the WordPress components package that we can use:
import { useBlockProps, InspectorControls } from '@wordpress/block-editor'
import { Spinner, RangeControl, Panel, PanelBody } from '@wordpress/components'
Now in our edit function let’s begin by destructuring “count” from our attributes.
registerBlockType('fsd/daily-post', {
edit({attributes, setAttributes}) {
const blockProps = useBlockProps();
const {count} = attributes
Next, we’ll add the Inspector Controls component. Inside this component, we add controls that display in the sidebar when we edit our block.
We’ll add two more components inside of this to wrap the Range Control, called Panel and Panel Body. This just adds a collapsible drawer with WordPress formatting like padding and margin, and gives us a title to display before the setting.
return (
<>
<div {...blockProps}>
//... rest of template
</div>
<InspectorControls>
<Panel header="Post Controls">
<PanelBody initialOpen={ true }>
<RangeControl
label="Posts to feature"
value={ count }
onChange={ ( val ) => setAttributes({count: val}) }
min={ 1 }
max={ 10 }
/>
</PanelBody>
</Panel>
</InspectorControls>
</>
);
The Range Control component has a lot of options, but they are fairly self-explanatory and mimic a typical range input.
The main critical parts are that we tie the value our count variable, and that the onChange attribute contains a function that updates our attribute with the new value. As long as these two things our present we have a functioning control tied into our block attributes that will maintain its value when we save and refresh!
Updating Our Endpoint
This is where things start getting a bit complicated, but bear with me, it will all tie together in the end.
The previous version of this build could just store one ID. This made it very simple to store and fetch, but now we are dealing with the possibility of multiple IDs which means our transient will now need to be an array that we can loop through. This adds some extra complexity.
Let’s jump in and modify the REST handler function in our rest-api.php file.
I suggest you copy/paste this into your file because I have pluralized the variable names which is easy to miss and cause hard to find errors later:
function fsd_rest_daily_post_handler($request){
$count = intval($request->get_param('count'));
$response = [];
$ids = get_transient( 'fsd_daily_post_ids' );
if(!$ids || !is_array($ids)){
$ids = fsd_generate_daily_post($count);
}
if(count($ids) !== $count ){
$ids = fsd_generate_daily_post($count);
}
foreach($ids as $id){
$post = [];
$post["url"] = get_permalink( $id );
$post["title"] = get_the_title( $id );
$post["img"] = get_the_post_thumbnail_url( $id, 'full' );
array_push($response, $post);
}
return $response;
}
First off, you can see we now are accepting the incoming request as an argument. We then grab a query parameter that we will call “count”. We will pass this in from our javascript file when we make the API request.
The other main difference here is our response will not be just an object with url, title, and image. Instead, we are looping through an array of post IDs and creating a response that will be an array of objects like this.
You can also see there is a second check for the transient. First we check whether it already exists (and make sure it is an array type, just for safety). Next, we compare the length of the existing transient to the count variable. If the desired count has changed, we will regenerate the transient as well.
We also pass the count variable into our generate daily post function which needs to be updated to accept it:
function fsd_generate_daily_post($count){
$posts = new WP_Query(array(
"posts_per_page" => $count,
"status" => "publish",
"post_type" => "post",
"orderby" => "rand"
));
$post_ids = [];
if($posts->have_posts()):
while($posts->have_posts( )): $posts->the_post();
array_push($post_ids, get_the_ID());
endwhile;
endif; wp_reset_postdata( );
$transient_value = $post_ids;
set_transient( 'fsd_daily_post_ids', $transient_value, DAY_IN_SECONDS);
return $transient_value;
}
We use the count parameter in our query to loop through and get all the post IDs and save them to an array for our transient (which has been renamed to be plural).
Updating Our Block’s Javascript File
If you are following along so far give yourself a pat on the back! This is a pretty in depth look into some advanced WordPress functionality!
Now that we have the REST endpoint and transient updated we need to update how our block works in the editor. Jump into your index.js file.
The first thing we need to do is update our state. Instead of have url, title, and image, we will now just have a property called posts that starts as an empty array. This will be updated to the response from the API endpoint we just modified that will be an array of our posts to feature. I also renamed the state to be plural to avoid confusion.
const [posts, setPosts] = useState({
isLoading: true,
posts: []
})
Now, let’s update where we actually make the request to our endpoint:
useEffect(() => {
const getPost = async () => {
const response = await apiFetch({
path: `fsd/v1/daily-post?count=${count}`
});
setPosts({
isLoading: false,
posts: response
})
}
getPost()
}, [count])
We have added our count as a query parameter since our endpoint is expecting this. We’ve also modified our setPosts function to match the response we are expecting to receive (an array of posts) and our new state.
Finally, we also added count as a dependency for the useEffect. This means anytime our count attribute changes this request will be fired off. This is perfect, because we want to re-run our function that updates the transients if the desired count of posts is changed by the user.
Awesome, now all that is left is to update our return statement to actually render an array of posts instead of just a single one:
<div {...blockProps}>
{posts.isLoading ? (
<Spinner />
) : (
posts.posts.map(post => (
<a href={post.url}>
<img src={post.img} />
<h3>{post.title}</h3>
</a>
))
)}
</div>
Don’t get tripped up by the posts.posts part, it looks funny, but this is just because of how our state is set up. We look inside our “posts” state for the property of “posts” which stores the array.
That’s it! our javascript is all ready to go.
Updating the block render template
This is the final step to updating our block to be able to display any number of daily random posts!
Luckily this is the easy part, most of it will look very familiar:
function fsd_daily_post_render_cb($atts){
$count = $atts["count"];
$ids = get_transient( 'fsd_daily_post_ids' );
if(!$ids || !is_array($ids)){
$ids = fsd_generate_daily_post($count);
}
if(count($ids) !== $count ){
$ids = fsd_generate_daily_post($count);
}
ob_start(); ?>
<?php foreach( $ids as $id): ?>
<div class="wp-block-udemy-plus-daily-post">
<h6><?php echo $title ?></h6>
<a href="<?php the_permalink( $id ); ?>">
<img src="<?php echo get_the_post_thumbnail_url( $id, 'full' ); ?>" alt="">
<h3><?php echo get_the_title($id) ?></h3>
</a>
</div>
<?php endforeach;?>
<?php return ob_get_clean();
}
First off, we are accepting our attributes as an argument to the render template now so that we have access to the desired count.
Then, we perform the same checks as in our API endpoint handler to see if the transient exists, if it is an array, and finally if it matches the length of our desired count. If any of those fail we simply call the generate function we updated earlier making sure to pass in the count variable.
Then, within our object buffer we just will wrap our template in a “for each” loop and iterate over the daily random posts rendering them out.
That’s it! Go ahead and save everything, make sure to run npm run start
and give you block a test!
Also if you are testing on a fresh install be sure to create a few sample posts first for it to be able to pull from!
Wrap up
Hopefully you were able to make it this far!
This is an advanced WordPress topic, so don’t be discouraged if you are running into errors or got stuck along the way! Feel free to take a look at the repo here for the finished state of this project (the part-2 branch), so that you can compare and contrast your code and get to the bottom of any issues.
Using what you’ve learned here, you can create some really cool functionality and expand upon this to allow taxonomy filtering, custom ordering, etc. Many of these modifications will actually be much easier, since the data type of the transient is now an array, so all you need to do is just pass the arguments through, not transform all the templates to handle arrays like we did this time!
Leave a Reply