Episode ini membahas form dan interaktivitas di Gatsby: form dengan client-side state, pengiriman lewat Netlify Forms dan Gatsby Functions, navigasi client-side, serta pola aksesibilitas dan feedback pengguna.

Situs statis bukan berarti statis total. Halaman yang di-generate saat build tetap bisa menampung form kontak, pencarian produk, atau filter data yang berjalan penuh di browser. Gatsby menyediakan React di sisi client dan Gatsby Functions di sisi server sehingga kombinasi keduanya menghasilkan interaksi yang lengkap.
Episode 10 membahas form dengan client-side state, pengiriman form lewat Netlify Forms dan Gatsby Functions, navigasi client-side serta perilaku dinamis, dan terakhir pola aksesibilitas serta feedback pengguna yang baik.
Form di Gatsby pada dasarnya adalah form React. Setiap input menyimpan nilainya di state, lalu state tersebut yang dipakai untuk validasi dan pengiriman. Setelah proses hydration selesai, seluruh handler bekerja normal seperti aplikasi React biasa.
import { useState } from "react"
const ContactForm = () => {
const [values, setValues] = useState({ name: "", email: "", message: "" })
const handleChange = (event) => {
const { name, value } = event.target
setValues((prev) => ({ ...prev, [name]: value }))
}
const handleSubmit = (event) => {
event.preventDefault()
console.log(values)
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Nama</label>
<input id="name" name="name" onChange={handleChange} />
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" onChange={handleChange} />
<label htmlFor="message">Pesan</label>
<textarea id="message" name="message" onChange={handleChange} />
<button type="submit">Kirim</button>
</form>
)
}
export default ContactFormPola atribut name yang sama pada setiap input membuat handler handleChange bisa dipakai ulang untuk semua field. Inilah yang disebut controlled component: nilai input berasal dari state, bukan sebaliknya.
Validasi bisa dilakukan sebelum pengiriman dengan memeriksa isi state. Namun validasi client saja tidak cukup — form tetap harus divalidasi di sisi server. handleSubmit di atas baru mencetak data ke konsol; pada bagian berikutnya kita ganti dengan pengiriman nyata.
Netlify Forms memungkinkan form Gatsby terkirim tanpa menulis server. Saat deploy ke Netlify, file HTML hasil build dipindai dan form yang memenuhi aturan otomatis menjadi endpoint. Tambahkan atribut name pada form dan field tersembunyi:
<form
name="contact"
method="POST"
data-netlify="true"
netlify-honeypot="bot-field"
>
<input type="hidden" name="form-name" value="contact" />
<input type="hidden" name="bot-field" />
<input type="text" name="name" placeholder="Nama" />
<input type="email" name="email" placeholder="Email" />
<textarea name="message" placeholder="Pesan" />
<button type="submit">Kirim</button>
</form>Saat pengguna submit, browser mengirim POST langsung ke Netlify dan submission muncul di dashboard Netlify. Netlify Forms paling cocok untuk situs yang memang di-host di Netlify dan tidak memerlukan logika server tambahan.
Jika kalian butuh logika khusus — validasi lanjutan, notifikasi email, atau integrasi CRM — Gatsby Functions menyediakan endpoint serverless di dalam project. Simpan file di folder src/api:
import type { GatsbyFunctionRequest, GatsbyFunctionResponse } from "gatsby"
const handler = (request: GatsbyFunctionRequest, response: GatsbyFunctionResponse) => {
const { name, email, message } = request.body
if (!email || !message) {
response.status(400).json({ error: "Email dan pesan wajib diisi" })
return
}
response.status(200).json({ ok: true, received: { name, email } })
}
export default handlerSetiap file di src/api otomatis menjadi endpoint /api/{nama-file} tanpa konfigurasi tambahan. Di development, Gatsby Functions berjalan bersama gatsby develop; di production, mereka didistribusikan sebagai fungsi tanpa server sesuai platform hosting.
Dari komponen form, kirim data dengan fetch:
const handleSubmit = async (event) => {
event.preventDefault()
setStatus("loading")
const res = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(values),
})
if (res.ok) {
setStatus("success")
} else {
setStatus("error")
}
}fetch("/api/contact", ...) mengirim JSON ke endpoint Gatsby Function. Response ok: true dari server dipakai untuk mengubah status UI menjadi sukses.
Navigasi antar halaman di Gatsby menggunakan komponen Link, bukan tag a, agar transisi dilakukan di client tanpa reload penuh. Untuk navigasi yang dipicu aksi lain, gunakan fungsi navigate:
import { navigate } from "gatsby"
const handleSuccess = () => navigate("/terima-kasih")navigate("/terima-kasih") berpindah halaman secara client-side sambil tetap memanfaatkan prefetching Gatsby. Prefetching membuat halaman tujuan sudah dimuat lebih awal, sehingga transisi terasa instan.
Untuk halaman yang hanya eksis di client, misalnya dashboard pengguna, gunakan pola client-only route dengan @reach/router yang sudah dibundel Gatsby. Buat halaman tempat kosong lalu render komponen dinamis yang membaca path dari prop location.
Setiap input wajib memiliki label. Gunakan htmlFor pada label dan id pada input agar keduanya terhubung, sehingga pembaca layar dapat mengumumkan label dengan benar. Untuk pesan error, gunakan aria-describedby agar assistive technology membacakan keterkaitan antara input dan pesan errornya.
Feedback status — loading, sukses, atau error — harus diumumkan tanpa mengubah fokus pengguna secara tiba-tiba. Gunakan region aria-live="polite" yang akan dibacakan pembaca layar saat kontennya berubah:
const FormStatus = ({ status }) => (
<div aria-live="polite">
{status === "loading" && "Mengirim data..."}
{status === "success" && "Terima kasih, pesan kalian terkirim."}
{status === "error" && "Terjadi kesalahan, coba lagi."}
</div>
)aria-live="polite" memastikan pembaca layar mengumumkan perubahan status tanpa interupsi. Selain itu, hindari menonaktifkan tombol submit tanpa alasan, dan berikan indikator visual yang jelas saat tombol sedang sibuk memproses.
Inti yang harus dibawa pulang:
Link dan navigate untuk navigasi client-side.aria-describedby, dan aria-live wajib untuk aksesibilitas.Di episode 11 selanjutnya kita akan membahas localization dan i18n — cara menginternasionalisasi situs Gatsby, multi-language routing, terjemahan konten, serta SEO untuk halaman berbahasa banyak.