Dies ist eine Fortsetzung meines letzten Artikels über „Rendern von externen API-Daten in WordPress-Blöcken im Front-End“. Darin lernten wir, wie man eine externe API nimmt und sie in einen Block integriert, der die abgerufenen Daten im Front-End einer WordPress-Website rendert.
Die Sache ist die, dass wir dies auf eine Weise erreicht haben, die uns daran hindert, die Daten im WordPress Block Editor zu sehen. Mit anderen Worten, wir können den Block auf einer Seite einfügen, erhalten aber keine Vorschau davon. Wir sehen den Block erst, wenn er veröffentlicht ist.
Betrachten wir den Beispiel-Block-Plugin, das wir im letzten Artikel erstellt haben, noch einmal. Nur dieses Mal werden wir das JavaScript- und React-Ökosystem von WordPress nutzen, um diese Daten auch im Back-End Block Editor abzurufen und zu rendern.
Arbeiten mit externen APIs in WordPress-Blöcken
- Daten im Frontend rendern
- Rendern von Daten im Back-End (Sie sind hier!)
- Erstellen einer benutzerdefinierten Einstellungs-UI
- Benutzerdefinierte Block-Einstellungen speichern
- Arbeiten mit Live-API-Daten (bald verfügbar)
Wo wir aufgehört haben
Zu Beginn, hier ist eine Demo, bei der wir im letzten Artikel gelandet sind und auf die Sie sich beziehen können. Möglicherweise haben Sie bemerkt, dass ich im letzten Artikel eine render_callback Methode verwendet habe, damit ich die Attribute in der PHP-Datei nutzen und den Inhalt rendern kann.
Nun, das mag in Situationen nützlich sein, in denen Sie möglicherweise native WordPress- oder PHP-Funktionen verwenden müssen, um dynamische Blöcke zu erstellen. Aber wenn Sie nur das JavaScript- und React-Ökosystem (speziell JSX) von WordPress nutzen möchten, um das statische HTML zusammen mit den in der Datenbank gespeicherten Attributen zu rendern, brauchen Sie sich nur auf die Funktionen Edit und Save des Block-Plugins zu konzentrieren.
- Die Funktion
Editrendert den Inhalt basierend auf dem, was Sie im Block Editor sehen möchten. Sie können hier interaktive React-Komponenten haben. - Die Funktion
Saverendert den Inhalt basierend auf dem, was Sie im Front-End sehen möchten. Sie können hier keine regulären React-Komponenten oder Hooks haben. Sie wird verwendet, um das statische HTML zurückzugeben, das zusammen mit den Attributen in Ihrer Datenbank gespeichert wird.
Die Funktion Save ist der Ort, an dem wir uns heute aufhalten. Wir können interaktive Komponenten im Front-End erstellen, aber dafür müssen wir sie manuell in einer Datei wie im letzten Artikel einbinden und darauf zugreifen, außerhalb der Save Funktion.
Daher werde ich dieselben Schritte wie im letzten Artikel behandeln, aber dieses Mal können Sie die Vorschau im Block Editor sehen, bevor Sie sie für das Front-End veröffentlichen.
Die Block-Props
Ich habe die Erklärungen zu den edit Funktions-Props im letzten Artikel absichtlich weggelassen, da dies den Fokus vom Hauptpunkt, dem Rendern, abgelenkt hätte.
Wenn Sie aus dem React-Umfeld kommen, werden Sie wahrscheinlich verstehen, wovon ich spreche, aber wenn Sie neu hier sind, empfehle ich Ihnen, Komponenten und Props in der React-Dokumentation zu lesen.
Wenn wir das props Objekt in der Konsole ausgeben, gibt es eine Liste von WordPress-Funktionen und Variablen zurück, die sich auf unseren Block beziehen.

Wir benötigen nur das attributes Objekt und die setAttributes Funktion, die ich aus dem props Objekt in meinem Code destrukturieren werde. Im letzten Artikel hatte ich den Code von RapidAPI modifiziert, damit ich die API-Daten über setAttributes() speichern kann. Props sind nur lesbar, daher können wir sie nicht direkt ändern.
Block-Props sind ähnlich wie Zustandsvariablen und setState in React, aber React arbeitet auf der Client-Seite und setAttributes() wird verwendet, um die Attribute nach dem Speichern des Beitrags dauerhaft in der WordPress-Datenbank zu speichern. Was wir also tun müssen, ist, sie in attributes.data zu speichern und sie dann als Anfangswert für die useState() Variable aufzurufen.
Die edit Funktion
Ich werde den HTML-Code, den wir in football-rankings.php im letzten Artikel verwendet haben, kopieren und einfügen und ihn ein wenig bearbeiten, um zum JavaScript-Umfeld zu wechseln. Erinnern Sie sich, wie wir im letzten Artikel zwei zusätzliche Dateien für das Front-End-Styling und die Skripte erstellt haben? Bei der heutigen Vorgehensweise ist es nicht notwendig, diese Dateien zu erstellen. Stattdessen können wir alles in die Edit Funktion verschieben.
Vollständiger Code
import { useState } from "@wordpress/element";
export default function Edit(props) {
const { attributes, setAttributes } = props;
const [apiData, setApiData] = useState(null);
function fetchData() {
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 }; // Deep clone the response data
setAttributes({ data: newData }); // Store the data in WordPress attributes
setApiData(newData); // Modify the state with the new data
})
.catch((err) => console.error(err));
}
return (
<div {...useBlockProps()}>
<button onClick={() => getData()}>Fetch data</button>
{apiData && (
<>
<div id="league-standings">
<div
className="header"
style={{
backgroundImage: `url(${apiData.response[0].league.logo})`,
}}
>
<div className="position">Rank</div>
<div className="team-logo">Logo</div>
<div className="team-name">Team name</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="points">Pts</div>
</div>
<div className="form-history">Form history</div>
</div>
<div className="league-table">
{/* Usage of [0] might be weird but that is how the API structure is. */}
{apiData.response[0].league.standings[0].map((el) => {
{/* Destructure the required data from all */}
const { played, win, draw, lose, goals } = el.all;
return (
<>
<div className="team">
<div class="position">{el.rank}</div>
<div className="team-logo">
<img src={el.team.logo} />
</div>
<div className="team-name">{el.team.name}</div>
<div className="stats">
<div className="games-played">{played}</div>
<div className="games-won">{win}</div>
<div className="games-drawn">{draw}</div>
<div className="games-lost">{lose}</div>
<div className="goals-for">{goals.for}</div>
<div className="goals-against">{goals.against}</div>
<div className="points">{el.points}</div>
</div>
<div className="form-history">
{el.form.split("").map((result) => {
return (
<div className={`result-${result}`}>{result}</div>
);
})}
</div>
</div>
</>
);
}
)}
</div>
</div>
</>
)}
</div>
);
}
Ich habe den React Hook useState() aus @wordpress/element eingebunden, anstatt ihn aus der React-Bibliothek zu verwenden. Das liegt daran, wenn ich ihn auf normale Weise laden würde, würde er React für jeden von mir verwendeten Block herunterladen. Aber wenn ich @wordpress/element verwende, wird er von einer einzigen Quelle geladen, nämlich der WordPress-Schicht über React.
Dieses Mal habe ich den Code auch nicht in useEffect(), sondern in eine Funktion verpackt, die nur beim Klicken auf einen Button aufgerufen wird, damit wir eine Live-Vorschau der abgerufenen Daten erhalten. Ich habe eine Zustandsvariable namens apiData verwendet, um die Liga-Tabelle bedingt zu rendern. Sobald der Button geklickt und die Daten abgerufen wurden, setze ich apiData in der fetchData() Funktion auf die neuen Daten, und es kommt zu einem erneuten Rendern mit dem verfügbaren HTML der Fußball-Rangliste.
Sie werden feststellen, dass nach dem Speichern des Beitrags und dem Aktualisieren der Seite die Liga-Tabelle verschwunden ist. Das liegt daran, dass wir für den Anfangswert von apiData einen leeren Zustand (null) verwenden. Wenn der Beitrag gespeichert wird, werden die Attribute im attributes.data Objekt gespeichert und wir rufen sie wie folgt als Anfangswert für die useState() Variable auf.
const [apiData, setApiData] = useState(attributes.data);
Die save Funktion
Mit der save Funktion werden wir fast dasselbe tun, aber sie ein wenig modifizieren. Zum Beispiel gibt es keinen Bedarf für den Button "Daten abrufen" im Front-End, und die Zustandsvariable apiData ist ebenfalls unnötig, da wir sie bereits in der edit Funktion prüfen. Aber wir brauchen eine zufällige apiData Variable, die attributes.data prüft, um das JSX bedingt zu rendern, sonst gibt es undefinierte Fehler und die Block Editor UI wird leer.
Vollständiger Code
export default function save(props) {
const { attributes, setAttributes } = props;
let apiData = attributes.data;
return (
<>
{/* Only render if apiData is available */}
{apiData && (
<div {...useBlockProps.save()}>
<div id="league-standings">
<div
className="header"
style={{
backgroundImage: `url(${apiData.response[0].league.logo})`,
}}
>
<div className="position">Rank</div>
<div className="team-logo">Logo</div>
<div className="team-name">Team name</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="points">Pts</div>
</div>
<div className="form-history">Form history</div>
</div>
<div className="league-table">
{/* Usage of [0] might be weird but that is how the API structure is. */}
{apiData.response[0].league.standings[0].map((el) => {
const { played, win, draw, lose, goals } = el.all;
return (
<>
<div className="team">
<div className="position">{el.rank}</div>
<div className="team-logo">
<img src={el.team.logo} />
</div>
<div className="team-name">{el.team.name}</div>
<div className="stats">
<div className="games-played">{played}</div>
<div className="games-won">{win}</div>
<div className="games-drawn">{draw}</div>
<div className="games-lost">{lose}</div>
<div className="goals-for">{goals.for}</div>
<div className="goals-against">{goals.against}</div>
<div className="points">{el.points}</div>
</div>
<div className="form-history">
{el.form.split("").map((result) => {
return (
<div className={`result-${result}`}>{result}</div>
);
})}
</div>
</div>
</>
);
})}
</div>
</div>
</div>
)}
</>
);
}
Wenn Sie die save Funktion modifizieren, nachdem ein Block bereits im Block Editor vorhanden ist, wird ein Fehler wie dieser angezeigt.

Das liegt daran, dass das Markup im gespeicherten Inhalt vom Markup in unserer neuen save Funktion abweicht. Da wir uns im Entwicklungsmodus befinden, ist es einfacher, den Block von der aktuellen Seite zu entfernen und ihn als neuen Block einzufügen – dadurch wird der aktualisierte Code verwendet und die Dinge sind wieder synchron.
Diese Situation, ihn zu entfernen und wieder hinzuzufügen, kann vermieden werden, wenn wir die render_callback Methode verwendet hätten, da die Ausgabe dynamisch ist und von PHP anstatt von der Save-Funktion gesteuert wird. Jede Methode hat also ihre eigenen Vor- und Nachteile.
Tom Nowell gibt in dieser Stack Overflow Antwort eine ausführliche Erklärung, was man in einer save Funktion nicht tun sollte.
Styling des Blocks im Editor und im Front-End
Was das Styling betrifft, so wird es fast dasselbe sein, was wir im letzten Artikel betrachtet haben, aber mit einigen kleineren Änderungen, die ich in den Kommentaren erklärt habe. Ich stelle hier lediglich den vollständigen Stil bereit, da dies nur ein Proof of Concept ist und nichts, was Sie kopieren und einfügen möchten (es sei denn, Sie benötigen wirklich einen Block zur Anzeige von Fußball-Ranglisten, der genau so gestylt ist). Und beachten Sie, dass ich immer noch SCSS verwende, das bei der Erstellung zu CSS kompiliert wird.
Editor-Stile
/* Target all the blocks with the data-title="Football Rankings" */
.block-editor-block-list__layout
.block-editor-block-list__block.wp-block[data-title="Football Rankings"] {
/* By default, the blocks are constrained within 650px max-width plus other design specific code */
max-width: unset;
background: linear-gradient(to right, #8f94fb, #4e54c8);
display: grid;
place-items: center;
padding: 60px 0;
/* Button CSS - From: https://getcssscan.com/css-buttons-examples - Some properties really not needed :) */
button.fetch-data {
align-items: center;
background-color: #ffffff;
border: 1px solid 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;
color: rgb(0 0 0 / 0.85);
cursor: pointer;
display: inline-flex;
font-family: system-ui, -apple-system, system-ui, "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px;
font-weight: 600;
justify-content: center;
line-height: 1.25;
margin: 0;
min-height: 3rem;
padding: calc(0.875rem - 1px) calc(1.5rem - 1px);
position: 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;
color: rgb(0, 0, 0, 0.65);
}
&:hover {
transform: translateY(-1px);
}
&:active {
background-color: #f0f0f1;
border-color: rgb(0 0 0 / 0.15);
box-shadow: rgb(0 0 0 / 0.06) 0 2px 4px;
color: rgb(0 0 0 / 0.65);
transform: translateY(0);
}
}
}
Front-End-Stile
/* Front-end block styles */
.wp-block-post-content .wp-block-football-rankings-league-table {
background: linear-gradient(to right, #8f94fb, #4e54c8);
max-width: unset;
display: grid;
place-items: center;
}
#league-standings {
width: 900px;
margin: 60px 0;
max-width: unset;
font-size: 16px;
.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-color: transparent;
background-repeat: no-repeat;
background-size: contain;
background-position: right;
.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;
top: 3px;
position: relative;
}
.stats {
display: flex;
gap: 15px;
& > div {
width: 30px;
text-align: center;
}
}
.last-5-games {
display: flex;
gap: 5px;
& > div {
width: 25px;
height: 25px;
text-align: center;
border-radius: 3px;
font-size: 15px;
& .result-W {
background: #347d39;
color: white;
}
& .result-D {
background: gray;
color: white;
}
& .result-L {
background: lightcoral;
color: white;
}
}
}
Wir fügen dies zu src/style.scss hinzu, was für das Styling sowohl im Editor als auch im Front-End zuständig ist. Ich kann die Demo-URL nicht teilen, da sie Editor-Zugriff erfordern würde, aber ich habe ein Video für Sie aufgezeichnet, um die Demo zu sehen.
Ziemlich schick, oder? Jetzt haben wir einen voll funktionsfähigen Block, der nicht nur im Front-End gerendert wird, sondern auch API-Daten abruft und direkt im Block Editor gerendert wird – mit einem Refresh-Button obendrauf!
Wenn wir jedoch die Block-Editor-Funktionen von WordPress vollständig nutzen wollen, sollten wir in Erwägung ziehen, einige der UI-Elemente des Blocks auf Block-Steuerelemente abzubilden, z. B. für Farben, Typografie und Abstände. Das ist ein guter nächster Schritt in der Lernreise der Blockentwicklung.
Ich habe einen Fehler in Ihrem Code gefunden
” getData()}>Daten abrufen”
Sollte fetchData() statt getData() sein