Skip to content

Migrasi Generic Heading & Generic Button Blocks

Gambaran Umum

Generic heading dan generic button block berbeda dari block lainnya: mereka tidak memiliki render.php. Block ini menyimpan HTML statis langsung di database — tidak ada dynamic rendering di server. Ini lebih cepat tapi kurang fleksibel.

Mengapa Tanpa render.php?

Block DENGAN render.php (banner, slide, slideshow, footer, header...):
→ Database menyimpan atribut/InnerBlocks
→ Server menjalankan PHP setiap request
→ HTML dihasilkan secara dinamis

Block TANPA render.php (generic heading, generic button):
→ Database menyimpan HTML statis lengkap
→ Server langsung menyajikan HTML dari database
→ Lebih cepat, tapi tidak bisa membuat keputusan dinamis

Generic Heading Block

Langkah 1: functions.php

php
// Hapus class JSXBlock sepenuhnya (sudah tidak dipakai)
// Hapus: new JSXBlock("genericheading", false);

// Tambahkan di ourNewBlocks():
register_block_type_from_metadata(__DIR__ . "/build/genericheading");

Langkah 2: Buat folder src/genericheading/

Duplikasi folder sederhana (misal src/archive/) → rename jadi genericheading.

PENTING: Hapus render.php dari folder ini! Block ini tidak memiliki server-side rendering.

Langkah 3: block.json (TANPA render property!)

json
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "ourblocktheme/genericheading",
  "title": "Generic Heading",
  "attributes": {
    "text": {
      "type": "string"
    },
    "size": {
      "type": "string",
      "default": "large"
    }
  },
  "editorScript": "file:./index.js"
}

Perhatikan:

  • TIDAK ADA properti "render" — jika ada, akan error karena file tidak ditemukan
  • Attributes text dan size dipindahkan dari JavaScript lama ke block.json

Langkah 4: edit.js

js
import { 
  RichText, 
  BlockControls, 
  useBlockProps 
} from "@wordpress/block-editor";
import { ToolbarGroup, ToolbarButton } from "@wordpress/components";

export default function Edit(props) {
  const blockProps = useBlockProps();

  function handleTextChange(x) {
    props.setAttributes({ text: x });
  }

  return (
    <div {...blockProps}>
      <BlockControls>
        <ToolbarGroup>
          <ToolbarButton
            isPressed={props.attributes.size === "large"}
            onClick={() => props.setAttributes({ size: "large" })}
          >
            Large
          </ToolbarButton>
          <ToolbarButton
            isPressed={props.attributes.size === "medium"}
            onClick={() => props.setAttributes({ size: "medium" })}
          >
            Medium
          </ToolbarButton>
          <ToolbarButton
            isPressed={props.attributes.size === "small"}
            onClick={() => props.setAttributes({ size: "small" })}
          >
            Small
          </ToolbarButton>
        </ToolbarGroup>
      </BlockControls>

      <RichText
        allowedFormats={["core/bold", "core/italic"]}
        tagName="h1"
        className={`headline headline--${props.attributes.size}`}
        value={props.attributes.text}
        onChange={handleTextChange}
      />
    </div>
  );
}

Penting API Version 3:

  • JSX dibungkus <div {...blockProps}> — bukan langsung <RichText>
  • Wrapper div menggantikan fungsi React Fragment dari versi lama
  • WordPress butuh wrapper ini untuk mendeteksi seleksi/klik block

Langkah 5: index.js (dengan Save Function)

js
import { registerBlockType } from "@wordpress/blocks";
import { RichText } from "@wordpress/block-editor";
import metadata from "./block.json";
import Edit from "./edit";

function Save(props) {
  function createTagName() {
    switch (props.attributes.size) {
      case "large":
        return "h1";
      case "medium":
        return "h2";
      case "small":
        return "h3";
    }
  }

  return (
    <RichText.Content
      tagName={createTagName()}
      value={props.attributes.text}
      className={`headline headline--${props.attributes.size}`}
    />
  );
}

registerBlockType(metadata.name, {
  edit: Edit,
  save: Save,
});

Perbedaan save function ini vs block lain:

  • Save function punya logika nyata (bukan hanya <InnerBlocks.Content />)
  • RichText.Content menghasilkan HTML statis yang disimpan di database
  • createTagName() menentukan tag HTML (h1/h2/h3) berdasarkan ukuran

Langkah 6: Update Template Files

Karena API Version 3 menambahkan wrapper div, class di template .html perlu diperbarui:

Sebelum (front-page.html):

html
<!-- wp:ourblocktheme/genericheading {"text":"...","size":"large"} -->
<h1 class="wp-block-ourblocktheme-genericheading headline headline--large">...</h1>
<!-- /wp:ourblocktheme/genericheading -->

Sesudah:

html
<!-- wp:ourblocktheme/genericheading {"text":"...","size":"large"} -->
<h1 class="headline headline--large">...</h1>
<!-- /wp:ourblocktheme/genericheading -->

→ Hapus wp-block-ourblocktheme-genericheading dari semua instance di template files.

Jika masih muncul error: Clear Customizations di editor (WordPress icon → 3 dots → Clear Customizations) agar menggunakan file template dari hard drive, bukan database.


Generic Button Block

Langkah 1: functions.php

php
// Hapus: new JSXBlock("genericbutton", false);
// Hapus seluruh class JSXBlock (sudah tidak diperlukan sama sekali)

// Tambahkan di ourNewBlocks():
register_block_type_from_metadata(__DIR__ . "/build/genericbutton");

Langkah 2: Duplikasi folder

Duplikasi src/genericheading/ → rename jadi genericbutton. Pastikan tidak ada render.php.

Langkah 3: block.json

json
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "ourblocktheme/genericbutton",
  "title": "Generic Button",
  "attributes": {
    "text": {
      "type": "string"
    },
    "size": {
      "type": "string",
      "default": "large"
    },
    "linkObject": {
      "type": "object",
      "default": {
        "url": ""
      }
    },
    "colorName": {
      "type": "string",
      "default": "blue"
    }
  },
  "editorScript": "file:./index.js"
}

Langkah 4: edit.js

js
import { RichText, InspectorControls, BlockControls, useBlockProps } from "@wordpress/block-editor";
import { PanelBody, PanelRow, ColorPalette, ToolbarGroup, ToolbarButton, Popover } from "@wordpress/components";
import { __experimentalLinkControl as LinkControl } from "@wordpress/block-editor";
import { useState } from "@wordpress/element";
import ourColors from "../../inc/ourColors";
import { link } from "@wordpress/icons";

export default function Edit(props) {
  const blockProps = useBlockProps();
  const [isLinkPickerVisible, setIsLinkPickerVisible] = useState(false);

  function handleTextChange(x) {
    props.setAttributes({ text: x });
  }

  function buttonHandler() {
    setIsLinkPickerVisible((prev) => !prev);
  }

  function handleLinkChange(newLink) {
    props.setAttributes({ linkObject: newLink });
  }

  // ... (sisanya sama dengan genericbutton.js lama)

  return (
    <div {...blockProps}>
      <BlockControls>
        <ToolbarGroup>
          <ToolbarButton onClick={buttonHandler} icon={link} />
        </ToolbarGroup>
        <ToolbarGroup>
          <ToolbarButton
            isPressed={props.attributes.size === "large"}
            onClick={() => props.setAttributes({ size: "large" })}
          >
            Large
          </ToolbarButton>
          <ToolbarButton
            isPressed={props.attributes.size === "medium"}
            onClick={() => props.setAttributes({ size: "medium" })}
          >
            Medium
          </ToolbarButton>
        </ToolbarGroup>
      </BlockControls>

      <InspectorControls>
        <PanelBody title="Color" initialOpen={true}>
          <PanelRow>
            <ColorPalette
              disableCustomColors={true}
              clearable={false}
              colors={ourColors}
              value={props.attributes.colorName}
              onChange={(x) => props.setAttributes({ colorName: x })}
            />
          </PanelRow>
        </PanelBody>
      </InspectorControls>

      <RichText
        allowedFormats={[]}
        tagName="a"
        className={`btn btn--${props.attributes.size} btn--${props.attributes.colorName}`}
        value={props.attributes.text}
        onChange={handleTextChange}
      />

      {isLinkPickerVisible && (
        <Popover position="middle center" onFocusOutside={() => setIsLinkPickerVisible(false)}>
          <LinkControl
            settings={[]}
            value={props.attributes.linkObject}
            onChange={handleLinkChange}
          />
          <br /><br />
        </Popover>
      )}
    </div>
  );
}

Path import ourColors:

src/genericbutton/edit.js → ../../inc/ourColors

Harus naik dua level karena berada di src/genericbutton/.

Langkah 5: index.js (dengan Save)

js
import { registerBlockType } from "@wordpress/blocks";
import metadata from "./block.json";
import Edit from "./edit";

function Save(props) {
  return (
    <a
      href={props.attributes.linkObject.url}
      className={`btn btn--${props.attributes.size} btn--${props.attributes.colorName}`}
    >
      {props.attributes.text}
    </a>
  );
}

registerBlockType(metadata.name, {
  edit: Edit,
  save: Save,
});

Setelah Semua Selesai: Hapus Folder Lama

Hapus folder our-blocks/ sepenuhnya!
Semua block sudah dimigrasi ke src/ folder.

Hapus Class PHP yang Tidak Dipakai

php
// Hapus dari functions.php:
// - Seluruh class PlaceholderBlock  ← sudah dihapus sebelumnya
// - Seluruh class JSXBlock          ← hapus sekarang

Ringkasan: Block Tanpa render.php

AspekDengan render.phpTanpa render.php
Save function<InnerBlocks.Content />Logika penuh (RichText.Content, dll)
HTML di databaseMinimal (inner blocks)HTML statis lengkap
Dynamic rendering✅ PHP setiap request❌ Langsung dari database
PerformanceSedikit lebih lambatSedikit lebih cepat
Contohbanner, slide, footergeneric heading, generic button

Error Umum yang Sering Muncul

ErrorSolusi
useBlockProps is not definedImport dari @wordpress/block-editor
props is not definedTambahkan props di parameter fungsi Edit
Block validation failedUpdate class di template .html files, lalu Clear Customizations
render.php not foundHapus property "render" dari block.json