There’ve been some new tutorials popping here on CSS-Tricks for working with WordPress blocks. One of them is an introduction to WordPress block development and it’s a good place to learn what blocks are and to register them in WordPress for use in pages and posts.
While the block basics are nicely covered in that post, I want to take it another step forward. You see, in that article, we learned the difference between rendering blocks in the back-end WordPress Block Editor and rendering them on the front-end theme. The example was a simple Pullquote Block that rendered different content and styles on each end.
Let’s go further and look at using dynamic content in a WordPress block. More specifically, let’s fetch data from an external API and render it on the front end when a particular block is dropped into the Block Editor.
We’re going to build a block that outputs data that shows soccer (er, football) rankings pulled from Api-Football.
There’s more than one way to integrate an API with a WordPress block! Since the article on block basics has already walked through the process of making a block from scratch, we’re going to simplify things by using the @wordpress/create-block
package to bootstrap our work and structure our project.
Initializing our block plugin
First things first: let’s spin up a new project from the command line:
npx @wordpress/create-block football-rankings
I normally would kick a project like this off by making the files from scratch, but kudos to the WordPress Core team for this handy utility!
Once the project folder has been created by the command, we technically have a fully-functional WordPress block registered as a plugin. So, let’s go ahead and drop the project folder into the wp-content/plugins
directory where you have WordPress installed (probably best to be working in a local environment), then log into the WordPress admin and activate it from the Plugins screen.
Now that our block is initialized, installed, and activated, go ahead and open up the project folder from at /wp-content/plugins/football-rankings
. You’re going to want to cd
there from the command line as well to make sure we can continue development.
These are the only files we need to concentrate on at the moment:
edit.js
index.js
football-rankings.php
The other files in the project are important, of course, but are inessential at this point.
Reviewing the API source
We already know that we’re using Api-Football which comes to us courtesy of RapidAPI. Fortunately, RapidAPI has a dashboard that automatically generates the required scripts we need to fetch the API data for the 2021 Premier League Standings.
If you want to have a look on the JSON structure, you can generate visual representation with JSONCrack.
Fetching data from the edit.js
file
I am going to wrap the RapidAPI code inside a React useEffect()
hook with an empty dependency array so that it runs only once when the page is loaded. This way, we prevent WordPress from calling the API each time the Block Editor re-renders. You can check that using wp.data.subscribe()
if you care to.
Here’s the code where I am importing useEffect()
, then wrapping it around the fetch()
code that RapidAPI provided:
/**
* The edit function describes the structure of your block in the context of the
* editor. This represents what the editor will render when the block is used.
*
* @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit
*
* @return {WPElement} Element to render.
*/ import { useEffect } from "@wordpress/element"; export default function Edit(props) { const { attributes, setAttributes } = props; useEffect(() => { const options = { method: "GET", headers: { "X-RapidAPI-Key": "Your Rapid API key", "X-RapidAPI-Host": "api-football-v1.p.rapidapi.com", }, }; fetch("https://api-football-v1.p.rapidapi.com/v3/standings?season=2021&league=39", options) .then( ( response ) => response.json() ) .then( ( response ) => { let newData = { ...response }; setAttributes( { data: newData } ); console.log( "Attributes", attributes ); }) .catch((err) => console.error(err));
}, []); return ( <p { ...useBlockProps() }> { __( "Standings loaded on the front end", "external-api-gutenberg" ) } </p> );
}
Notice that I have left the return
function pretty much intact, but have included a note that confirms the football standings are rendered on the front end. Again, we’re only going to focus on the front end in this article — we could render the data in the Block Editor as well, but we’ll leave that for another article to keep things focused.
Storing API data in WordPress
Now that we are fetching data, we need to store it somewhere in WordPress. This is where the attributes.data
object comes in handy. We are defining the data.type
as an object
since the data is fetched and formatted as JSON. Make sure you don’t have any other type or else WordPress won’t save the data, nor does it throw any error for you to debug.
We define all this in our index.js
file:
registerBlockType( metadata.name, { edit: Edit, attributes: { data: { type: "object", }, }, save,
} );
OK, so WordPress now knows that the RapidAPI data we’re fetching is an object. If we open a new post draft in the WordPress Block Editor and save the post, the data is now stored in the database. In fact, if we can see it in the wp_posts.post_content
field if we open the site’s database in phpMyAdmin, Sequel Pro, Adminer, or whatever tool you use.
Outputting JSON data in the front end
There are multiple ways to output the data on the front end. The way I’m going to show you takes the attributes that are stored in the database and passes them as a parameter through the render_callback
function in our football-rankings.php
file.
I like keeping a separation of concerns, so how I do this is to add two new files to the block plugin’s build
folder: frontend.js
and frontend.css
(you can create a frontend.scss
file in the src
directory which compiled to CSS in the build
directory). This way, the back-end and front-end codes are separate and the football-rankings.php
file is a little easier to read.
/explanation Referring back to the introduction to WordPress block development, there are editor.css
and style.css
files for back-end and shared styles between the front and back end, respectively. By adding frontend.scss
(which compiles to frontend.css
, I can isolate styles that are only intended for the front end.
Before we worry about those new files, here’s how we call them in football-rankings.php
:
/**
* Registers the block using the metadata loaded from the `block.json` file.
* Behind the scenes, it registers also all assets so they can be enqueued
* through the block editor in the corresponding context.
*
* @see https://developer.wordpress.org/reference/functions/register_block_type/
*/
function create_block_football_rankings_block_init() { register_block_type( __DIR__ . '/build', array( 'render_callback' => 'render_frontend' ));
}
add_action( 'init', 'create_block_football_rankings_block_init' ); function render_frontend($attributes) { if( !is_admin() ) { wp_enqueue_script( 'football_rankings', plugin_dir_url( __FILE__ ) . '/build/frontend.js'); wp_enqueue_style( 'football_rankings', plugin_dir_url( __FILE__ ) . '/build/frontend.css' ); // HIGHLIGHT 15,16,17,18 } ob_start(); ?> <div class="football-rankings-frontend" id="league-standings"> <div class="data"> <pre> <?php echo wp_json_encode( $attributes ) ?> </pre> </div> <div class="header"> <div class="position">Rank</div> <div class="team-logo">Logo</div> <div class="team-name">Team name</div> <div class="stats"> <div class="games-played">GP</div> <div class="games-won">GW</div> <div class="games-drawn">GD</div> <div class="games-lost">GL</div> <div class="goals-for">GF</div> <div class="goals-against">GA</div> <div class="points">Pts</div> </div> <div class="form-history">Last 5 games</div> </div> <div class="league-table"></div> </div> <?php return ob_get_clean();
}
Since I am using the render_callback()
method for the attributes, I am going to handle the enqueue manually just like the Block Editor Handbook suggests. That’s contained in the !is_admin()
condition, and is enqueueing the two files so that we avoid enqueuing them while using the editor screen.
Now that we have two new files we’re calling, we’ve gotta make sure we are telling npm
to compile them. So, do that in package.json
, in the scripts
section:
"scripts": { "build": "wp-scripts build src/index.js src/frontend.js", "format": "wp-scripts format", "lint:css": "wp-scripts lint-style", "lint:js": "wp-scripts lint-js", "packages-update": "wp-scripts packages-update", "plugin-zip": "wp-scripts plugin-zip", "start": "wp-scripts start src/index.js src/frontend.js"
},
Another way to include the files is to define them in the block metadata contained in our block.json
file, as noted in the introduction to block development:
"viewScript": [ "file:./frontend.js", "example-shared-view-script" ], "style": [ "file:./frontend.css", "example-shared-style" ],
The only reason I’m going with the package.json
method is because I am already making use of the render_callback()
method.
Rendering the JSON data
In the rendering part, I am concentrating only on a single block. Generally speaking, you would want to target multiple blocks on the front end. In that case, you need to make use of document.querySelectorAll()
with the block’s specific ID.
I’m basically going to wait for the window to load and grab data for a few key objects from JSON and apply them to some markup that renders them on the front end. I am also going to convert the attributes
data to a JSON object so that it is easier to read through the JavaScript and set the details from JSON to HTML for things like the football league logo, team logos, and stats.
The “Last 5 games” column shows the result of a team’s last five matches. I have to manually alter the data for it since the API data is in a string format. Converting it to an array can help make use of it in HTML as a separate element for each of a team’s last five matches.
import "./frontend.scss"; // Wait for the window to load
window.addEventListener( "load", () => { // The code output const dataEl = document.querySelector( ".data pre" ).innerHTML; // The parent rankings element const tableEl = document.querySelector( ".league-table" ); // The table headers const tableHeaderEl = document.querySelector( "#league-standings .header" ); // Parse JSON for the code output const dataJSON = JSON.parse( dataEl ); // Print a little note in the console console.log( "Data from the front end", dataJSON ); // All the teams let teams = dataJSON.data.response[ 0 ].league.standings[ 0 ]; // The league logo let leagueLogoURL = dataJSON.data.response[ 0 ].league.logo; // Apply the league logo as a background image inline style tableHeaderEl.style.backgroundImage = `url( ${ leagueLogoURL } )`; // Loop through the teams teams.forEach( ( team, index ) => { // Make a div for each team const teamDiv = document.createElement( "div" ); // Set up the columns for match results const { played, win, draw, lose, goals } = team.all; // Add a class to the parent rankings element teamDiv.classList.add( "team" ); // Insert the following markup and data in the parent element teamDiv.innerHTML = ` <div class="position"> ${ index + 1 } </div> <div class="team-logo"> <img src="${ team.team.logo }" /> </div> <div class="team-name">${ team.team.name }</div> <div class="stats"> <div class="games-played">${ played }</div> <div class="games-won">${ win }</div> <div class="games-drawn">${ draw }</div> <div class="games-lost">${ lose }</div> <div class="goals-for">${ goals.for }</div> <div class="goals-against">${ goals.against }</div> <div class="points">${ team.points }</div> </div> <div class="form-history"></div> `; // Stringify the last five match results for a team const form = team.form.split( "" ); // Loop through the match results form.forEach( ( result ) => { // Make a div for each result const resultEl = document.createElement( "div" ); // Add a class to the div resultEl.classList.add( "result" ); // Evaluate the results resultEl.innerText = result; // If the result a win if ( result === "W" ) { resultEl.classList.add( "win" ); // If the result is a draw } else if ( result === "D" ) { resultEl.classList.add( "draw" ); // If the result is a loss } else { resultEl.classList.add( "lost" ); } // Append the results to the column teamDiv.querySelector( ".form-history" ).append( resultEl ); }); tableEl.append( teamDiv ); });
});
As far as styling goes, you’re free to do whatever you want! If you want something to work with, I have a full set of styles you can use as a starting point.
I styled things in SCSS since the @wordpress/create-block
package supports it out of the box. Run npm run start
in the command line to watch the SCSS files and compile them to CSS on save. Alternately, you can use npm run build
on each save to compile the SCSS and build the rest of the plugin bundle.
View SCSS
body { background: linear-gradient(to right, #8f94fb, #4e54c8);
} .data pre { display: none;
} .header { display: grid; gap: 1em; padding: 10px; grid-template-columns: 1fr 1fr 3fr 4fr 3fr; align-items: center; color: white; font-size: 16px; font-weight: 600; background-repeat: no-repeat; background-size: contain; background-position: right;
} .frontend#league-standings { width: 900px; margin: 60px 0; max-width: unset; font-size: 16px; .header { .stats { display: flex; gap: 15px; & > div { width: 30px; } } }
} .league-table { background: white; box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px; padding: 1em; .position { width: 20px; } .team { display: grid; gap: 1em; padding: 10px 0; grid-template-columns: 1fr 1fr 3fr 4fr 3fr; align-items: center; } .team:not(:last-child) { border-bottom: 1px solid lightgray; } .team-logo img { width: 30px; } .stats { display: flex; gap: 15px; } .stats > div { width: 30px; text-align: center; } .form-history { display: flex; gap: 5px; } .form-history > div { width: 25px; height: 25px; text-align: center; border-radius: 3px; font-size: 15px; } .form-history .win { background: #347d39; color: white; } .form-history .draw { background: gray; color: white; } .form-history .lost { background: lightcoral; color: white; }
}
Here’s the demo!
Check that out — we just made a block plugin that fetches data and renders it on the front end of a WordPress site.
We found an API, fetch()
-ed data from it, saved it to the WordPress database, parsed it, and applied it to some HTML markup to display on the front end. Not bad for a single tutorial, right?
Again, we can do the same sort of thing so that the rankings render in the Block Editor in addition to the theme’s front end. But hopefully keeping this focused on the front end shows you how fetching data works in a WordPress block, and how the data can be structured and rendered for display.
Rendering External API Data in WordPress Blocks on the Front End originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.