Preview Professor di Editor (Custom REST API & apiFetch)
Gambaran Umum
Dua video ini membahas cara menampilkan preview HTML professor di admin editor secara real-time. Pendekatan yang diambil: membuat custom REST API endpoint yang mengembalikan HTML dari PHP, lalu memanggilnya dari React menggunakan apiFetch, useState, dan useEffect.
Mengapa Pendekatan ini?
Opsi yang Dipertimbangkan
| Pendekatan | Pro | Kontra |
|---|---|---|
| Rebuild template di JSX | Modern, React-native | Duplikasi kode — template harus ditulis 2x (PHP + JSX) |
| Tambah property HTML ke getEntityRecords | Satu query | Berat — 70-80 professor × 5-10KB HTML per item |
| Custom REST endpoint per professor | Hemat bandwidth, satu template | Perlu dangerouslySetInnerHTML |
| React framework (Frontity) | Template sekali pakai | Butuh Node.js hosting, di luar scope |
Pilihan: Custom REST API endpoint — hanya ambil HTML untuk professor yang dipilih, template PHP dipakai ulang.
Membuat Custom REST API Endpoint
Daftarkan Endpoint di PHP
Di featured-professor.php, tambah action di constructor:
php
function __construct() {
add_action('init', [$this, 'onInit']);
add_action('rest_api_init', [$this, 'profHTML']);
}Method profHTML — Register Route
php
function profHTML() {
register_rest_route('featuredProfessor/v1', 'getHTML', array(
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'getProfHTML']
));
}- Namespace:
featuredProfessor/v1 - Route:
getHTML - URL lengkap:
your-site.com/wp-json/featuredProfessor/v1/getHTML WP_REST_Server::READABLE= hanya menerima GET request
Method getProfHTML — Callback
php
function getProfHTML($data) {
return generateProfessorHTML($data['profId']);
}$dataparameter otomatis berisi query variables dari URL- Contoh:
?profId=43→$data['profId']=43 - Memanfaatkan function
generateProfessorHTML()yang sudah dibuat sebelumnya
Testing Endpoint
Kunjungi di browser:
http://your-site.com/wp-json/featuredProfessor/v1/getHTML?profId=43- WordPress otomatis meng-escape HTML menjadi valid JSON
- Otomatis menambah backslash pada quotes di dalam HTML
Menampilkan Preview di React (Editor)
Import yang Dibutuhkan
jsx
import { useState, useEffect } from "react"
import apiFetch from "@wordpress/api-fetch"State untuk Preview HTML
jsx
function EditComponent(props) {
const [thePreview, setThePreview] = useState('');
// ...
}useEffect — Watch profId Changes
jsx
useEffect(function() {
if (props.attributes.profId) {
async function go() {
const response = await apiFetch({
path: `/featuredProfessor/v1/getHTML?profId=${props.attributes.profId}`,
method: 'GET'
});
setThePreview(response);
}
go();
}
}, [props.attributes.profId]);Cara kerja:
useEffectdipanggil setiap kaliprofIdberubah- Cek apakah
profIdada (bukan kosong/undefined) - Buat async function
go()di dalam (workaround — useEffect tidak bisa langsung async) apiFetchmengirim GET request ke REST endpoint kita- Tidak perlu tulis full URL — WordPress otomatis menambahkan domain +
/wp-json - Response (string HTML) disimpan ke state
thePreview go()langsung dipanggil setelah didefinisikan
Render Preview dengan dangerouslySetInnerHTML
jsx
<div dangerouslySetInnerHTML={{ __html: thePreview }}></div>- React secara default meng-escape semua HTML untuk keamanan
dangerouslySetInnerHTMLmemaksa React merender HTML mentah- Nama sengaja "menakutkan" — hanya gunakan jika Anda percaya sumber HTML-nya
- Dalam kasus ini, HTML berasal dari PHP server kita sendiri → aman
Catatan Keamanan: XSS & the_title()
Masalah Potensial
Jika judul professor mengandung kode jahat:
Dr. Meows A Lot<script>alert('hello')</script>JavaScript tersebut akan dieksekusi di front-end karena kita merender HTML mentah.
Solusi
Opsi 1: Escape HTML
php
echo esc_html(get_the_title());→ Menampilkan kode sebagai teks, tidak dieksekusi
Opsi 2: Strip semua tags
php
echo wp_strip_all_tags(get_the_title());→ Menghapus semua HTML tags sepenuhnya
Pertimbangan
- Ini bukan ancaman besar karena admin sendiri yang menulis judul
- Tapi best practice tetap meng-escape, terutama saat menggunakan
dangerouslySetInnerHTML - Untuk
the_content(), biasanya kita izinkan HTML (strong, em, dll.) - Keputusan ada di tangan developer tergantung konteks
Kode Lengkap Edit Component (Sejauh Ini)
jsx
import "./index.scss"
import { useState, useEffect } from "react"
import { useSelect } from "@wordpress/data"
import apiFetch from "@wordpress/api-fetch"
function EditComponent(props) {
const [thePreview, setThePreview] = useState('');
const allProfs = useSelect(function(select) {
return select('core').getEntityRecords('postType', 'professor', { per_page: -1 });
});
useEffect(function() {
if (props.attributes.profId) {
async function go() {
const response = await apiFetch({
path: `/featuredProfessor/v1/getHTML?profId=${props.attributes.profId}`,
method: 'GET'
});
setThePreview(response);
}
go();
}
}, [props.attributes.profId]);
if (allProfs == undefined) {
return <p>Loading...</p>;
}
return (
<div className="featured-professor-wrapper">
<div className="professor-select-container">
<select onChange={e => props.setAttributes({ profId: e.target.value })}>
<option>Select a professor</option>
{allProfs.map(prof => {
return (
<option value={prof.id} selected={props.attributes.profId == prof.id}>
{prof.title.rendered}
</option>
);
})}
</select>
</div>
<div dangerouslySetInnerHTML={{ __html: thePreview }}></div>
</div>
);
}
wp.blocks.registerBlockType('ourplugin/featured-professor', {
title: 'Professor Callout',
icon: 'welcome-learn-more',
category: 'common',
attributes: {
profId: { type: 'string' }
},
edit: EditComponent,
save: function() {
return null;
}
});