Cara menambahkan interaktivitas pada framework Astro

Interaktivitas di Astro

Cara menambahkan interaktivitas pada framework Astro

Β· tutorials Β· 7 min readΒ·

Sekilas mengenai Astro

Astro merupakan web framework untuk membuat website yang content-driven semacam blog atau marketing. Astro mengambil jalan sebagai UI-agnostik, yang artinya bisa dikombinasikan dengan berbagai UI framework kesukaan kalian macam React, Vue, Svelte dan lainnya. Astro juga terkenal karena menjadi pelopor bagi arsitektur terbaru di dunia persilatan web framework, yakni Island Arsitektur yang di framework lain mungkin lebih dikenal dengan partial/selective hydration.

Menambahkan UI framework ke Astro

Astro menjadi jembatan bagi yang ingin mulai mengurangi kode-kode JavaScript tapi masih galau. Kamu bisa menulis kode Astro dalam satu projek atau mengkombinasikannya dengan framework lain kalau memang dibutuhkan. Sebagian besar framework populer sudah tersedia official integrasinya dengan Astro, sehingga mudah dan aman untuk menambahkan ke projek kita.

Misalnya kalau ingin menambahkan React ke dalam projek Astro, bisa semudah dengan command:

zsh
npx astro add react

Atau install manual dengan cara:

zsh
npm install @astrojs/react react react-dom

Kemudian tambahkan konfigurasi di astro.config.*

astro.config.ts
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
export default defineConfig({
// ...
integrations: [react()],
});

Untuk menambahkan kode dari framework lain ke dalam komponen Astro sendiri bisa semudah import seperti komponen lainnya:

src/pages/sample.astro
---
import MyReactComponent from '../components/MyReactComponent.jsx';
---
<html>
<body>
<h1>Use React components directly in Astro!</h1>
<MyReactComponent />
</body>
</html>

Masalahnya secara bawaan, kode ini akan di render di server baik runtime maupun build-time dan di serve sebagai statik HTML sehingga akan kehilangan reaktivitasnya.

Astro telah menyediakan ekstra directive, jika membutuhkan kode framework yang interaktif.

src/pages/sample.astro
---
// Example: hydrating framework components in the browser.
import InteractiveButton from '../components/InteractiveButton.jsx';
import InteractiveCounter from '../components/InteractiveCounter.jsx';
import InteractiveModal from '../components/InteractiveModal.svelte';
---
<!-- This component's JS will begin importing when the page loads -->
<InteractiveButton client:load />
<!-- This component's JS will not be sent to the client until
the user scrolls down and the component is visible on the page -->
<InteractiveCounter client:visible />
<!-- This component won't render on the server, but will render on the client when the page loads -->
<InteractiveModal client:only="svelte" />

Selengkapnya bisa dibaca mengenai Client Directives yang tersedia di Astro.

Menambah Interaktifitas di Astro

Untuk menambahkan interaktivitas, kamu sebenarnya bisa dengan mudah menggunakan UI framework seperti yang telah dijelaskan singkat pada bagian sebelumnya. Namun yang mau kita bahas adalah menambahkan interaktivitas langsung pada komponen Astro.

Contoh kasusnya misalnya kamu hanya membutuhkan interaktivitas sederhana yang sepertinya terasa overkill untuk menambahkan UI framework tambahan ke dalam projek kalian. Misalnya β€œcuma” untuk membuka-tutup (toggle) suatu menu sidebar, melakukan DOM manipulasi sederhana, atau sekedar menyampaikan suatu state sederhana ke tempat lain.

Astro seperti halnya HTML, bisa menambahkan kode client-side JavaScript pada markup mereka, contohnya:

src/components/sample.astro
<h1>Welcom to Astro!</h1>
<script>
console.log('[Client Log]: Hello from Astro!')
</script>

Mengetahui hal ini kita bisa punya beberapa pilihan alternatif API dari web platform yang memang memungkinkan kita dengan mudah menambahkan interaktivitas langsung dari kode Astro komponen.

Menyelipkan event handler

Event di sini maksudnya event-event yang memang sudah tersedia by-default di berbagai komponen bawaan HTML, misalnya kita ingin menambahkan click event pada suatu tombol, maka kita bisa menambahkan event listener seperti biasa contohnya:

src/components/AlertButton.astro
<button id="alert">Click me!</button>
<script>
const alertBtn = document.querySelector('#alert');
alertBtn.addEventListener('click', () => {
alert('Button was clicked!');
});
</script>

Dari kode contoh di atas kita bisa menambahkan akrobat sedikit dengan Vanilla JavaScript yang mungkin sudah mulai jarang digunakan setelah kebanyakan tenggelam dengan UI framework kekinian.

Untuk kasus lain, misalnya toggle menu, kita bisa memanfaatkan DOM manipulation sederhana juga, misal:

src/components/MenuToggle.astro
<header id="header" data-state="close" class="group">
<nav class="fixed right-4 top-16 z-20 min-w-[200px] transform transition-all duration-500 group-[[data-menu=close]]:translate-x-[110%] group-[[data-menu=open]]:translate-x-0">
<ul>
<li>Home</li>
<li>Blog</li>
<li>About</li>
</ul>
</nav>
<button id="menu-toggle">Toggle menu!</button>
</header>
<script>
const toggleBtn = document.querySelector('#menu-toggle');
toggleBtn.addEventListener('click', () => {
const header = document.querySelector('#header');
const prevState = header.getAttribute('data-state');
const nextState = prevState === 'open' ? 'close' : 'open';
header.setAttribute('data-state', nextState);
});
</script>

Sedikit trik, kalau dengan Tailwind kita bisa menambahkan style berdasarkan parent-nya, sehingga untuk kasus sederhana seperti open/close state ini kita bisa langsung menyimpannya di atribut data-* seperti pada contoh kode di atas dan menggunakan selector class group-[[data-menu=open]] atau group-[[data-menu=close]] untuk mendeteksi kondisi state dalam keadaan terbuka atau tertutup.

Menggunakan Custom Element dari Web Component

Salah satu fitur utama pada web component adalah kita bisa membuat custom element. Custom element merupakan custom tag HTML yang behavior-nya sudah didefinisikan oleh si web developer, biasanya berupa perpanjangan dari dari berbagai element yang tersedia di peramban.

Misal pada contoh kode berikut kita akan menambakan custom element pada Astro komponen berupa tombol hati yang akan mendeteksi sudah berapa kali tombol tersebut ditekan. Perhatikan contoh kode di bawah:

src/components/AstroHeart.astro
<!-- Wrap the component elements in our custom element β€œastro-heart”. -->
<astro-heart>
<button aria-label="Heart">πŸ’œ</button> Γ— <span>0</span>
</astro-heart>
<script>
// Define the behaviour for our new type of HTML element.
class AstroHeart extends HTMLElement {
connectedCallback() {
let count = 0;
const heartButton = this.querySelector('button');
const countSpan = this.querySelector('span');
// Each time the button is clicked, update the count.
heartButton.addEventListener('click', () => {
count++;
countSpan.textContent = count.toString();
});
}
}
// Tell the browser to use our AstroHeart class for <astro-heart> elements.
customElements.define('astro-heart', AstroHeart);
</script>

Dengan memanfaatkan custom element, kalian bisa menambahkan interaktivitas yang lebih kaya dengan kode yang lebih oke tapi masih pakai Vanilla JavaScript sehingga tidak diperlukan library tambahan.

Berbagi state antar komponen

Selain untuk menambahkan interaktivitas sederhana, script tambahan biasanya juga dibutuhkan untuk saling berbagi state antar satu komponen dengan komponen lain, maupun dengan UI framework yang digunakan di dalam projek tersebut. Ini contoh kasus umum ketika menggunakan Astro karena biasanya memang kita akan gonta-ganti antara kode Astro dengan kode UI framework dalam satu projek.

Berdasarkan dokumentasi resmi β€œShare state between islands”, mereka merekomendasikan untuk menggunakan Nano Stores, ataupun alternative serupa yang sudah ada bawaan di masing-masing UI framework, seperti:

Namun yang akan kita bahas lebih lanjut adalah cara alternatif lain memanfaatkan web platform API.

Membagikan custom event

Event pada dasarnya adalah pub-sub (publish-subscribe) konsep dimana ada yang mempublikasikan sesuatu dan ada yang akan berlangganan terhadap suatu event. Kamu bisa membaca selengkapnya mengenai cara mengirim dan berlangganan suatu event di β€œCreating and triggering events”.

Mengambil contoh kasus yang sama dengan sebelumnya, kita akan membuat tombol hati sederhana dengan text yang menampilkan jumlah klik. Namun kali ini kita akan memisah keduanya menjadi 2 komponen terpisah. Satu untuk menampilkan tombolnya, kita akan buat berkas HeartBtn.astro dan untuk text-nya kita akan membuat CounterText.astro dan dijadikan 1 pada index.astro. Berikut contoh kode index.astro:

src/pages/index.astro
---
import HeartBtn from '../components/HeartBtn.astro'
import CounterText from '../components/CounterText.astro'
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Astro</title>
</head>
<body>
<h1>Astro: Send & Subscribe Custom Event</h1>
<div style="display:inline-block;">
<HeartBtn />
<CounterText />
</div>
</body>
</html>

Pada komponen HeartBtn.astro, kita akan membuat tombol dan akan mengirimkan Custom Event saat ditekan. Berikut contoh kode implementasinya:

src/components/HeartBtn.astro
<button id="btn">πŸ’œ</button>
<script>
const btn = document.querySelector('#btn');
let state = 0
btn.addEventListener('click', () => {
state += 1
// Sending custom event with the detail state
btn.dispatchEvent(new CustomEvent("increment", { detail: state }))
});
</script>

Sedangkan pada CounterText.astro, kita akan berlanganan state yang dikirimkan oleh tombol tersebut, contoh kodenya:

src/components/CounterText.astro
<span>x <span id="text">0</span></span>
<script>
const btn = document.querySelector('#btn');
const p = document.querySelector('#text');
btn.addEventListener('increment', (e) => {
p.innerText = e.detail
});
</script>

Terlihat overkill ya? Mungkin karena sebenarnya bisa juga langsung manipulasi dengan innerText pada HeartBtn.astro ke komponen sebelahnya. Seperti yang sering kita lakukan di jaman jQuery merajalela. Yang bikin repot karena Custom Event ini menempel pada element sumber. Jadi untuk bisa subcribe, kita mesti select dulu komponen sumbernya. Tapi ini penyederhanaan kasus saja, siapa tau nanti kalian ketemu kasus dimana perlu menggunakan Custom Event.

Lihat kode selengkapnya pada Codesandbox contoh mengirim dan berlangganan Custom Event

Menggunakan postMessage

Pada dasarnya ini mirip dengan Custom Event, hanya saja event-nya akan ditempelkan langsung ke objek global window, jadinya kita tidak perlu select ke component sumber.

Berikut contoh kode melempar state dari Astro ke React dengan memanfaatkan postMessage, dengan contoh kasus membuat fitur yang sama, berikut kode index.astro:

src/pages/index.astro
---
import HeartBtn from "../components/HeartBtn.astro";
import CounterText from "../components/CounterText.jsx";
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Astro</title>
</head>
<body>
<h1>Astro: window.postMessage</h1>
<div style="display:inline-block;">
<HeartBtn />
<CounterText client:only="react" />
</div>
</body>
</html>

Pada komponen HeartBtn.astro, kita akan mengubah sedikit dari yang sebelumnya melempar Custom Event, di sini kita akan melempar postMessage, lihat kode berikut:

src/components/HeartBtn.astro
<button id="btn">πŸ’œ</button>
<script>
const btn = document.querySelector('#btn');
let state = 0
if (btn) {
btn.addEventListener("click", () => {
state += 1;
window.postMessage({ type: "increment", state });
});
}
</script>

Dan kode komponen CounterText akan kita ubah dari komponen Astro menjadi komponen React yang subscribe event dari postMessage tersebut, berikut contoh kodenya:

src/components/CounterText.jsx
import { useEffect, useState } from "react";
const CounterText = () => {
const [count, setCount] = useState(0);
const eventHandler = (e) => {
if (e?.data?.type === "increment") {
setCount(e?.data?.state);
}
};
useEffect(() => {
window.addEventListener("message", eventHandler);
return () => {
window?.removeEventListener("message", eventHandler);
};
}, []);
return (
<span>
x <span id="text">{count}</span>
</span>
);
};
export default CounterText;

Lihat kode selengkapnya pada Codesandbox contoh menggunakan postMessage

Bahan bacaan tambahan


πŸ‘‹ Sekian dan terima kasih

Maaf-maaf aja kalau gak bermanfaat πŸ™‡πŸ˜­


Foto cover diambil dari laman Pexel, Foto oleh Đức Phúc

More posts