This can be a continuation of my final article about “Rendering Exterior API Knowledge in WordPress Blocks on the Entrance Finish”. In that final one, we discovered take an exterior API and combine it with a block that renders the fetched information on the entrance finish of a WordPress website.
The factor is, we achieved this in a method that forestalls us from seeing the info within the WordPress Block Editor. In different phrases, we will insert the block on a web page however we get no preview of it. We solely get to see the block when it’s printed.
Let’s revisit the instance block plugin we made within the final article. Solely this time, we’re going to utilize the JavaScript and React ecosystem of WordPress to fetch and render that information within the back-end Block Editor as nicely.
The place we left off
As we kick this off, right here’s a demo the place we landed within the final article that you may reference. You will have observed that I used a render_callback
technique within the final article in order that I could make use of the attributes within the PHP file and render the content material.
Effectively, which may be helpful in conditions the place you may need to make use of some native WordPress or PHP operate to create dynamic blocks. However if you wish to make use of simply the JavaScript and React (JSX, particularly) ecosystem of WordPress to render the static HTML together with the attributes saved within the database, you solely must give attention to the Edit
and Save
capabilities of the block plugin.
- The
Edit
operate renders the content material based mostly on what you wish to see within the Block Editor. You possibly can have interactive React elements right here. - The
Save
operate renders the content material based mostly on what you wish to see on the entrance finish. You can’t have the the common React elements or the hooks right here. It’s used to return the static HTML that’s saved into your database together with the attributes.
The Save
operate is the place we’re hanging out at this time. We will create interactive elements on the front-end, however for that we have to manually embody and entry them exterior the Save
operate in a file like we did within the final article.
So, I’m going to cowl the identical floor we did within the final article, however this time you possibly can see the preview within the Block Editor earlier than you publish it to the entrance finish.
The block props
I deliberately overlooked any explanations concerning the edit
operate’s props within the final article as a result of that will have taken the main focus off of the primary level, the rendering.
If you’re coming from a React background, you’ll seemingly perceive what’s that I’m speaking about, however if you’re new to this, I’d suggest trying out elements and props within the React documentation.
If we log the props
object to the console, it returns a listing of WordPress capabilities and variables associated to our block:
We solely want the attributes
object and the setAttributes
operate which I’m going to destructure from the props
object in my code. Within the final article, I had modified RapidAPI’s code in order that I can retailer the API information by means of setAttributes()
. Props are solely readable, so we’re unable to switch them immediately.
Block props are just like state variables and setState
in React, however React works on the shopper facet and setAttributes()
is used to retailer the attributes completely within the WordPress database after saving the put up. So, what we have to do is save them to attributes.information
after which name that because the preliminary worth for the useState()
variable.
edit
operate
The I’m going to copy-paste the HTML code that we utilized in football-rankings.php
within the final article and edit it a bit to shift to the JavaScript background. Bear in mind how we created two extra recordsdata within the final article for the entrance finish styling and scripts? With the best way we’re approaching issues at this time, there’s no must create these recordsdata. As an alternative, we will transfer all of it to the Edit
operate.
Full code
import { useState } from "@wordpress/aspect";
export default operate Edit(props) {
const { attributes, setAttributes } = props;
const [apiData, setApiData] = useState(null);
operate fetchData() {
const choices = {
technique: "GET",
headers: {
"X-RapidAPI-Key": "Your Fast 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",
choices
)
.then((response) => response.json())
.then((response) => {
let newData = { ...response }; // Deep clone the response information
setAttributes({ information: newData }); // Retailer the info in WordPress attributes
setApiData(newData); // Modify the state with the brand new information
})
.catch((err) => console.error(err));
}
return (
<div {...useBlockProps()}>
<button onClick={() => getData()}>Fetch information</button>
{apiData && (
<>
<div id="league-standings">
<div
className="header"
fashion={{
backgroundImage: `url(${apiData.response[0].league.emblem})`,
}}
>
<div className="place">Rank</div>
<div className="team-logo">Emblem</div>
<div className="team-name">Crew title</div>
<div className="stats">
<div className="games-played">GP</div>
<div className="games-won">GW</div>
<div className="games-drawn">GD</div>
<div className="games-lost">GL</div>
<div className="goals-for">GF</div>
<div className="goals-against">GA</div>
<div className="factors">Pts</div>
</div>
<div className="form-history">Kind historical past</div>
</div>
<div className="league-table">
{/* Utilization of [0] could be bizarre however that's how the API construction is. */}
{apiData.response[0].league.standings[0].map((el) => {
{/* Destructure the required information from all */}
const { performed, win, draw, lose, objectives } = el.all;
return (
<>
<div className="workforce">
<div class="place">{el.rank}</div>
<div className="team-logo">
<img src={el.workforce.emblem} />
</div>
<div className="team-name">{el.workforce.title}</div>
<div className="stats">
<div className="games-played">{performed}</div>
<div className="games-won">{win}</div>
<div className="games-drawn">{draw}</div>
<div className="games-lost">{lose}</div>
<div className="goals-for">{objectives.for}</div>
<div className="goals-against">{objectives.towards}</div>
<div className="factors">{el.factors}</div>
</div>
<div className="form-history">
{el.type.break up("").map((outcome) => {
return (
<div className={`result-${outcome}`}>{outcome}</div>
);
})}
</div>
</div>
</>
);
}
)}
</div>
</div>
</>
)}
</div>
);
}
I’ve included the React hook useState()
from @wordpress/aspect
quite than utilizing it from the React library. That’s as a result of if I had been to load the common method, it will obtain React for each block that I’m utilizing. But when I’m utilizing @wordpress/aspect
it hundreds from a single supply, i.e., the WordPress layer on prime of React.
This time, I’ve additionally not wrapped the code inside useEffect()
however inside a operate that is named solely when clicking on a button in order that now we have a stay preview of the fetched information. I’ve used a state variable known as apiData
to render the league desk conditionally. So, as soon as the button is clicked and the info is fetched, I’m setting apiData
to the brand new information contained in the fetchData()
and there’s a rerender with the HTML of the soccer rankings desk obtainable.
You’ll discover that after the put up is saved and the web page is refreshed, the league desk is gone. That’s as a result of we’re utilizing an empty state (null
) for apiData
‘s preliminary worth. When the put up saves, the attributes are saved to the attributes.information
object and we name it because the preliminary worth for the useState()
variable like this:
const [apiData, setApiData] = useState(attributes.information);
save
operate
The We’re going to do nearly the identical actual factor with the save
operate, however modify it a bit bit. For instance, there’s no want for the “Fetch information” button on the entrance finish, and the apiData
state variable can be pointless as a result of we’re already checking it within the edit
operate. However we do want a random apiData
variable that checks for attributes.information
to conditionally render the JSX or else it should throw undefined errors and the Block Editor UI will go clean.
Full code
export default operate save(props) {
const { attributes, setAttributes } = props;
let apiData = attributes.information;
return (
<>
{/* Solely render if apiData is accessible */}
{apiData && (
<div {...useBlockProps.save()}>
<div id="league-standings">
<div
className="header"
fashion={{
backgroundImage: `url(${apiData.response[0].league.emblem})`,
}}
>
<div className="place">Rank</div>
<div className="team-logo">Emblem</div>
<div className="team-name">Crew title</div>
<div className="stats">
<div className="games-played">GP</div>
<div className="games-won">GW</div>
<div className="games-drawn">GD</div>
<div className="games-lost">GL</div>
<div className="goals-for">GF</div>
<div className="goals-against">GA</div>
<div className="factors">Pts</div>
</div>
<div className="form-history">Kind historical past</div>
</div>
<div className="league-table">
{/* Utilization of [0] could be bizarre however that's how the API construction is. */}
{apiData.response[0].league.standings[0].map((el) => {
const { performed, win, draw, lose, objectives } = el.all;
return (
<>
<div className="workforce">
<div className="place">{el.rank}</div>
<div className="team-logo">
<img src={el.workforce.emblem} />
</div>
<div className="team-name">{el.workforce.title}</div>
<div className="stats">
<div className="games-played">{performed}</div>
<div className="games-won">{win}</div>
<div className="games-drawn">{draw}</div>
<div className="games-lost">{lose}</div>
<div className="goals-for">{objectives.for}</div>
<div className="goals-against">{objectives.towards}</div>
<div className="factors">{el.factors}</div>
</div>
<div className="form-history">
{el.type.break up("").map((outcome) => {
return (
<div className={`result-${outcome}`}>{outcome}</div>
);
})}
</div>
</div>
</>
);
})}
</div>
</div>
</div>
)}
</>
);
}
If you’re modifying the save
operate after a block is already current within the Block Editor, it will present an error like this:
That’s as a result of the markup within the saved content material is totally different from the markup in our new save
operate. Since we’re in improvement mode, it’s simpler to take away the bock from the present web page and re-insert it as a brand new block — that method, the up to date code is used as an alternative and issues are again in sync.
This case of eradicating it and including it once more will be prevented if we had used the render_callback
technique because the output is dynamic and managed by PHP as an alternative of the save operate. So every technique has it’s personal benefits and downsides.
Tom Nowell supplies a radical clarification on what to not do in a save
operate in this Stack Overflow reply.
Styling the block within the editor and the entrance finish
Relating to the styling, it’ll be nearly the identical factor we checked out within the final article, however with some minor adjustments which I’ve defined within the feedback. I’m merely offering the total kinds right here since that is solely a proof of idea quite than one thing you wish to copy-paste (except you actually do want a block for displaying soccer rankings styled similar to this). And be aware that I’m nonetheless utilizing SCSS that compiles to CSS on construct.
Editor kinds
/* Goal all of the blocks with the data-title="Soccer Rankings" */
.block-editor-block-list__layout
.block-editor-block-list__block.wp-block[data-title="Football Rankings"] {
/* By default, the blocks are constrained inside 650px max-width plus different design particular code */
max-width: unset;
background: linear-gradient(to proper, #8f94fb, #4e54c8);
show: grid;
place-items: middle;
padding: 60px 0;
/* Button CSS - From: https://getcssscan.com/css-buttons-examples - Some properties actually not wanted :) */
button.fetch-data {
align-items: middle;
background-color: #ffffff;
border: 1px strong rgb(0 0 0 / 0.1);
border-radius: 0.25rem;
box-shadow: rgb(0 0 0 / 0.02) 0 1px 3px 0;
box-sizing: border-box;
colour: rgb(0 0 0 / 0.85);
cursor: pointer;
show: inline-flex;
font-family: system-ui, -apple-system, system-ui, "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px;
font-weight: 600;
justify-content: middle;
line-height: 1.25;
margin: 0;
min-height: 3rem;
padding: calc(0.875rem - 1px) calc(1.5rem - 1px);
place: relative;
text-decoration: none;
transition: all 250ms;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
vertical-align: baseline;
width: auto;
&:hover,
&:focus {
border-color: rgb(0, 0, 0, 0.15);
box-shadow: rgb(0 0 0 / 0.1) 0 4px 12px;
colour: rgb(0, 0, 0, 0.65);
}
&:hover {
rework: translateY(-1px);
}
&:energetic {
background-color: #f0f0f1;
border-color: rgb(0 0 0 / 0.15);
box-shadow: rgb(0 0 0 / 0.06) 0 2px 4px;
colour: rgb(0 0 0 / 0.65);
rework: translateY(0);
}
}
}
Entrance-end kinds
/* Entrance-end block kinds */
.wp-block-post-content .wp-block-football-rankings-league-table {
background: linear-gradient(to proper, #8f94fb, #4e54c8);
max-width: unset;
show: grid;
place-items: middle;
}
#league-standings {
width: 900px;
margin: 60px 0;
max-width: unset;
font-size: 16px;
.header {
show: grid;
hole: 1em;
padding: 10px;
grid-template-columns: 1fr 1fr 3fr 4fr 3fr;
align-items: middle;
colour: white;
font-size: 16px;
font-weight: 600;
background-color: clear;
background-repeat: no-repeat;
background-size: include;
background-position: proper;
.stats {
show: flex;
hole: 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;
.place {
width: 20px;
}
.workforce {
show: grid;
hole: 1em;
padding: 10px 0;
grid-template-columns: 1fr 1fr 3fr 4fr 3fr;
align-items: middle;
}
.workforce:not(:last-child) {
border-bottom: 1px strong lightgray;
}
.team-logo img {
width: 30px;
prime: 3px;
place: relative;
}
.stats {
show: flex;
hole: 15px;
& > div {
width: 30px;
text-align: middle;
}
}
.last-5-games {
show: flex;
hole: 5px;
& > div {
width: 25px;
top: 25px;
text-align: middle;
border-radius: 3px;
font-size: 15px;
& .result-W {
background: #347d39;
colour: white;
}
& .result-D {
background: grey;
colour: white;
}
& .result-L {
background: lightcoral;
colour: white;
}
}
}
We add this to src/fashion.scss
which takes care of the styling in each the editor and the frontend. I won’t be able to share the demo URL since it will require editor entry however I’ve a video recorded so that you can see the demo:
Fairly neat, proper? Now now we have a totally functioning block that not solely renders on the entrance finish, but in addition fetches API information and renders proper there within the Block Editor — with a refresh button as well!
But when we wish to take full benefit of the WordPress Block Editor, we ought to think about mapping a number of the block’s UI components to block controls for issues like setting colour, typography, and spacing. That’s a pleasant subsequent step within the block improvement studying journey.