WordPress.org

WordPress Developer Blog

Converting your shortcodes to blocks

One of the things I love about blocks is that they make for a great “upgrade path” from shortcodes. Traditionally, shortcodes were a way that plugin developers could provide users the ability to add specific plugin functionality anwhere on their site. But shortcodes are not very user friendly, nor was hunting down the relevant data you needed to render the correct data for the shortcode. Converting existing shortcodes to blocks provides a much greater user experience in all aspects.

In this article I’m going to share with you some tips and tricks that I wish I had known when I was first creating blocks from my shortcodes.

Learning React

Before we go any further, if you want to build blocks, it’s a really good idea to understand the basics of how React works. If you want to build on top of the framework, whether you use the React JSX route, or the plain JavaScript route, you will need to understand React fundamentals.

Here, there are a couple of resources I can recommend. There are the Introduction to Block Development: Build your first custom block and Converting a Shortcode to a Block courses available on Learn WordPress, as well as A PHP developer’s guide to getting started with block development post on this blog.

Finally, I suggested checking out the Using the create-block Tool tutorial, for those who prefer to learn by watching video tutorials.

Not every shortcode should be a block!

Let’s get this out of the way, not every shortcode should be a block. The WordPress shortcode system is powerful and has its place. I certainly don’t see it being removed at any time in the future. Heck, even the block editor has a shortcode block.

There are many cases where a shortcode still makes sense over a block. One of my favorite examples of a shortcode that doesn’t need to be a block is the time shortcode that’s used on the Make WordPress blogs.

[time relative]Tuesday 07:00 UTC[/time]

If you’ve never seen it in use before, this seemingly simple shortcode facilitates the meetings of all of the WordPress contributor teams. When used correctly, it will render the time in the reader’s local time zone. This is extremely valuable for a globally distributed team of WordPress contributors, as it makes sure they can plan and schedule their meetings correctly.

This type of inline rendering is not possible with blocks at the moment. There are some first attempts to find a way to make those dynamic strings possible without using a short code, but it’s not available yet. If this is something you’re interesting in following, it is very fascinating Discussion, which includes a draft PR for implementation.

That having been said, while it might one day be possible to turn this into a block, it’s not something I would do as there’s no real benefit for the writer to add this as a block.

When considering if your shortcode should be turned into a block, I’d suggest asking yourself the following questions:

  • Does the shortcode allow for multiple pieces of functionality? If the shortcode just does one thing and does it well, it can probably be left as a shortcode. The time shortcode above is a good example of this.
  • Will making this into a block mean it’s easier to use? Often, shortcodes that have loads of attributes, or combinations of attributes that render information differently, make the best targets for block support.
  • Does the shortcode render complicated HTML, that will make a huge difference to the user if they can see it in the editor? Often, this question will override your answers from the first two questions. A single-use shortcode or a shortcode that’s easy to use as-is might still make sense as a block if rendering its output in the editor makes life easier for the user.

I generally find if I can answer yes to any of these questions, it makes sense to convert the shortcode to a block.

Finally, I would add that when I say “convert to ” I don’t mean “replace with”. For backward compatibility purposes, your shortcodes should still always exist and work. Especially if you have a popular product that folks have been using for a while. You wouldn’t want an update to suddenly render all their content broken!

Converting shortcodes

I enjoy using Create Block so much, I recorded a 47-minute tutorial on it back in 2020, which I turned into a shorter series of tutorials for Learn WordPress. If you’ve never tried to convert a shortcode to a block, I recommend watching them first, to get a good foundation of what you’ll need to get started.

If you have a plugin with a shortcode that just renders a specific set of HTML, you use Create Block to scaffold a new block, copy the relevant code from the scaffolded block to your plugin, and then convert the HTML from your shortcode into JSX to be returned in the block’s edit and save functions.

This was the process I followed when I started converting the Seriously Simple Podcasting plugin to support blocks in late 2020.

Authors note, because of the amount of code that made up some of the blocks I was building at the time, I’m going to simplify the actual code used. However, you can browse the repo at this point in time via this url. Where I talk about the implementation I will also link directly to the actual files I’m referencing in the article.

The first block I created was the based on the Player Shortcode block.

The initial implementation of the player shortcode called the load_media_player function which originally used the built-in WordPress wp_audio_shortcode function and renders the audio player that’s included in WordPress, based on mediaelement.js

In version 1.19.2 of the plugin we added a more feature rich player we dubbed the “HTML5 player” that used a more complicated markup, CSS for styling and JavaScript for functionality like skipping forward and backwards.

The shortcode could be added to a podcast enabled post without any attributes, and it would render the player for that post.

Turning this shortcode into a block was an easier implementation than anything else I still had to do, so I tackled this first.

I scaffolded a new plugin using Create Block, and then copied over the package.json file, and the entire src directory.

At the time, we were using grunt to minify our CSS and JavaScript assets for production use, so I had to merge the scaffolded package.json with what we already had.

Then I took the HTML from the player template, copied it verbatim over the JSX in the return function of the block’s edit function.

<div class="castos-player <?php echo $player_mode ?>-mode" 
data-episode="<?php echo $episode_id?>">
    <div class="player">
    <!-- player image -->
        <div class="player__body">
            <!-- player body -->
        </div>
    </div>
</div>
edit: () => {
    return (
        <div class="castos-player <?php echo $player_mode ?>-mode" 
data-episode="<?php echo $episode_id?>">
            <div class="player">
                <!-- player image -->
                <div class="player__body">
                    <!-- player body -->
                </div>
            </div>
        </div>
    );
},

My next step was to replace any HTML attributes with DOM properties (for example changing class="castos-player" className={castos-player}), and convert any PHP variables into JavaScript ones. I’d have to somehow pass the variables like the episode_id and player_mode to the block, which I figured I could do using the props object passed to the edit or save functions.

edit: (props) => {
    const episode_id = props.episode_id;
    const player_mode = props.player_mode;
    return (
        <div className="castos-player {player_mode}-mode" data-episode="{episode_id}">
            <div class="player">
                <!-- player image -->
                <div class="player__body">
                    <!-- player body -->
                </div>
            </div>
        </div>
    );
},

This was one of the early lessons I learned about JSX, it’s very much like HTML, but it’s not exactly HTML. However, because it’s very similar to HTML, it’s easy to convert existing HTML to JSX, and then make the necessary changes to make it work.

For the first iteration I also neeeded to wire up the CSS from the shortcode. At the time I managed this using enqueue_block_editor_assets action, but today I’d probably do it using the style option in the block.json metadata.

This code is now lost to time, but for the first iteration I just passed the variables as hardcoded attributes to the block’s props, so that I could see it all working. I would figure out how to update the block code to make it possible for the user to choose the episode and player mode later.

Once I code the player code to render in the editor, I made sure to update the save function with the same code, so that the block would render the same way on the front end.

I was happily surprised to see it all work, and my player HTML rendered, with styling intact, in both the editor and the front end.

This did however make the edit and save functions HUGE! And I knew that they would grow even more, because I was planning to add additional functionality to the block, like the ability to select the episode to be rendered. It was at this point I remembered learning about creating custom React components.

Leverage custom components

Components in React are essentially a way to avoid repeating your code. If you’ve ever built blocks using things like BlockControls, or RichText, you’re using a component. Components are great because they allow you to write once, and use everywhere. In this case, moving the Castos Player HTML to a component made complete sense, because then I could write and manage the code once, and reuse it for both the edit and save functions of the block.

The first thing I did was move the JSX for the player into a /components/CastosPlayer.js component, and update the variables to be passed in as props.

class CastosPlayer extends Component {
    render() {
        return (
            <div className="{this.props.className} {this.props.player_mode}-mode" data-episode="{this.props.episode_id}">
                <div class="player">
                    <!-- player image -->
                    <div class="player__body">
                        <!-- player body -->
                    </div>
                </div>
            </div>
        );
    }
}
export default EditCastosPlayer;

Then I imported the component into the main block index.js file

import CastosPlayer from "./components/CastosPlayer";

Finally, I used the JSX syntax to implement the component in my edit function, passing the variables as props to the component.

edit: (props) => {
    const episode_id = props.episode_id;
    const player_mode = props.player_mode;
    const className = props.className;
    return (
        <CastosPlayer
            className="{className}
            episodeId={episode_id}
            playerMode={player_mode}
        />
    );
},

If I remember correctly, at the time, I also had to pass the className variable to the component.

However, as the functionality for the edit function grew, I started realising that it would make sense to have a separate EditCastosPlayer component as well. So I did just that:

import CastosPlayer from "./CastosPlayer";

class EditCastosPlayer extends Component {
    constructor({className, episodeId, playerMode}) {
        super(...arguments);
        this.state = {
            className: className,
            episodeId: episodeId,
            playerMode: playerMode,
        };
    }
    render() {
        return (
            <CastosPlayer
                className={this.state.className}
                episodeId={this.state.episodeId}
                playerMode={this.state.playerMode}
            />
        );
    }
}
export default EditCastosPlayer;

I used the this.state object to store any specific variables passed to the component. I’m not sure if this was the right way to do this, but it worked.

I then needed to import the EditPlayer component into the main block file:

import EditPlayer from './components/EditPlayer';

My final block edit function ended up looking pretty tidy.

edit: EditPlayer,

I found custom components to be so useful, that I ended up creating custom components for pretty much everything eventually, and it’s something I still do today when I see code that’s going to be repeated, or could be simplified by separating it into its own component.

Importing packages and components

In the code examples above, you’ll see I’m using the import keyword to import a specific component.

If you happen to be looking through the code repo, in some places I import things from WordPress packages like this:

const {Component} = wp.element;

Yet in other places I import like this.

import {Component} from "@wordpress/element";

This is purely down to me not fully understanding how importing code works in JavaScript. In the first example, I’m importing the Component class from the global wp package, which exists whenever the block editor is active. In the second, I’m importing from the @wordpress/element package, which is a node dependency of the plugin. Either way, when the code is transpiled, it’s amounts to the same thing. However, as I’m relying on JSX and the npm build step to transpile the code, I should have used the second method throughout.

Power block data using apiFetch

Of course, as the block functionality grew, I needed to get the data from the WordPress REST API. Because we wanted to allow the user to select the episode to be rendered, I needed to get a list of all the episodes. I used the apiFetch package to do this.

The beauty of using apiFetch is that it’s geared towards using the WordPress REST API, so all I had to do was import it into my code, and then pass it an endpoint of episodes, for it would return the data.

import apiFetch from '@wordpress/api-fetch';
const populateEpisodes = () => {
    let fetchPost = 'ssp/v1/episodes';
    apiFetch({path: fetchPost}).then(posts => {
        // do something with the returned posts
    });
}

Power block data using core-data

Something I didn’t know about until recently, is that the core-data package exists. It’s a package that allows you to get data from the WordPress REST API, without having to use apiFetch.

The difference between using apiFetch and the core-data package, is that core-data is more similar to using WordPress’ built-in functions to get data.

For example, this is what the code would look like using core-data to replicate the WordPress get_users() function, to return a list of users from the WordPress site.

import {useSelect} from "@wordpress/data";

const users = useSelect( ( select ) => {
        return select( 'core' ).getUsers();
}, [] );

This is pretty cool if the only data you need to query is core WordPress data, like users, posts, etc.

WordPress core contributor Adam Zieliński recently created an amazing course on Learn WordPress on how to use core data, which I recommend checking out.

Conditional rendering

One of the other cool things I learned about why React is so powerful is the ability to conditionally render differnt things, based on the value of a boolen variable.

So for example, when the user added the Castos Player block to their post or page, they should first see a select box, populated with all the episodes fetch via the apiFetch call above.

<div className={this.state.className}>
    Select podcast Episode
    <select ref={this.episodeRef}>
        {this.state.episodes.map((item, key) =>
            <option value={item.id}>{item.title}</option>
        )}
    </select>
    <button onClick={activateEpisode}>Go</button>
</div>

However, once they select the episode, and click the button to trigger the activateEpisode function, the select box should disappear, and the Castos Player should be rendered.

So, I created a boolean variable named editing in the EditPlayer component constructor, and set it to true by default.

class EditPlayer extends Component {
    constructor({className}) {
        super(...arguments);
        let editing = true;

Then, in the activateEpisode function, once the episode has been selected and the relevant episode data loaded, I’d set it to false.

Finally, I’d wrap the component’s return in a conditional, and then either return the select code, or return the CastosPlayer component, depending on the value of editing.

if (editing) {
    return (
        <div className={this.state.className}>
            Select podcast Episode
            <select ref={this.episodeRef}>
                {this.state.episodes.map((item, key) =>
                    <option value={item.id}>{item.title}</option>
                )}
            </select>
            <button onClick={activateEpisode}>Go</button>
        </div>
    );
} else {
    return [
        controls, (
            <CastosPlayer className={this.state.className}
                          episodeImage={this.state.episode.episodeImage}
                          episodeFileUrl={this.state.episode.episodeFileUrl}
                          episodeTitle={this.state.episode.episodeTitle}
                          episodeDuration={this.state.episode.episodeDuration}
                          episodeDownloadUrl={this.state.episode.episodeDownloadUrl}
            />
        )];
}

It’s a tiny thing, but I was really pleased with that solution. It saved users the hassle of having to hunt down the episode ID, and enter it in a field in the block Sidebar somewhere. They could just add the block, select the episode, and it would render it right in the editor!

Resources to learn more

I’d be lying if I said I manage to figure this all out by myself. Besides completing a React for beginners course, I found various resources extremely helpful.

Whenever folks ask me where to find help, my first suggestion is to try and find an experienced contributor to lean on. I am grateful for my many developer friends in the WordPress community, who were willing to help me answer specific questions. Many Gutenberg contributors are sponsored full time to work on Gutenberg, and so I’d recommend trying to reach out to one of them directly via the Make WordPress Slack. I’m sure they’d be happy to help.

Discussions on the Gutenberg repository on GitHub are also a good place to ask questions, as well as the WordPress Development Stack Exchange.

You are also very welcome to reach out to me directly. I’m happy to help where I can, and if I don’t have the answers, I’ll do my best to either find out, or connect you with the right person. Feel free to comment on this post, and I’ll do my best to help, or redirect you to someone who can help.

I’d also recommend reading the Block Editor Handbook. It’s a great resource for learning about the various components and packages that make up Gutenberg. But take the time to read it thoroughly, and then ask questions when you don’t understand something. What I found really helpful was to try and solve the current problem with the info in the handbook, and then when I reached out to folks directly, to include my code, and the handbook link, so they could see what I’d tried, and then help me figure out what I was missing. This is imminently more helpful than just asking a generalised question without providing any context.

The Learn WordPress site is also a great resource for learning WordPress development. Besides the tutorials and courses I’ve linked to in this post there are often hosted online workshops on Block development.

Last, but very much not least, look at other block code. Find plugins in the WordPress plugin repository that have implemented blocks, dive into what they have done, and how they’ve solved problems.

Happy coding!

Thanks to @bph and @webcommsat for reviewing the post

Categories: , ,

Leave a Reply

Your email address will not be published. Required fields are marked *