Cara Ubah HTML yang Sudah Ada Jadi Data Berulang di React JS, TypeScript (Free Code)
Daftar Isi PendahuluanDummy Code (Free)MulaiBuat Proyek ReactStep 1Step 2Step 3Step 4FocusAmbil KodenyaCovert Ke React Pake WebsiteData ReactTypes (TypeScript)Download LibrarySetelah Semua di Atas Bisa di Tulis GiniSwiperPengkondisianPerulanganLinkRupiah (Bonus)Hasil Akhir Semua Tutorial KitaPenutup Pendahuluan Kalau kamu lgi pegang file HTML dan mulai mikir, “Gimana ya caranya ngeubah ini jadi komponen React pakai TypeScript?” tenang, kamu ada di tempat yang tepat. Di sini, kita bakal bongkar bareng gimana cara nyulap HTML biasa jadi struktur React yang rapi dan reusable. Gak cuma itu, kamu juga bakal dapet kodenya secara gratis, tinggal ubah dan sesuaikan. Kita bakal bahas gimana data bisa ditampilkan, dan gimana cara ngelola data yang berulang biar gak bikin kepala cenat-cenut. Siap? Yuk mulai! Dummy Code (Free) Di tutorial kali ini, kita bakal sru-seruan bareng ngebedah proyek keren dari Shaynakit. Kabar baiknya: ini 100% gratis, bro! Dan yang makin mantep, kodenya udah siap tempur. Jadi lo nggak perlu mulai dari nol, tinggal colok, modif dikit, langsung jalan. Nggak ribet, nggak bikin kening berkerut, pokoknya ngoding jadi fun banget! Shaynakit ini tuh kayak buffet buat front-end developer tinggal pilih desain yang lo suka, semua udah sepaket sama kodenya. Ada versi gratis, ada juga yang premium. Tapi santai, yang gratis pun udah cukup loyal kok. Biasanya yang dibatesin cuma jumlah halaman aja. Nah, kalau yang premium? Itu sih udah kayak pesta coding semua elemen tinggal comot, tinggal pakai, beres! Sekarang masuk ke inti dari semuanya: gimana cara dapetin template gratisnya. Gampang banget, nggak perlu mantra khusus. Cukup ikutin langkah-langkah ini: Buka situs utamanya di sini: https://shaynakit.com/landing.Klik menu Register atau langsung aja meluncur ke https://shaynakit.com/register buat daftar akun dulu.Setelah berhasil daftar dan login, buka halaman template yang mau dipakai di sini: https://shaynakit.com/details/mahouse-real-estate-html-tailwind-css-template.Klik tombol Download, lalu pilih opsi Free Trial.Lanjut klik Start Today buat mulai akses gratisnya.Setelah itu, balik lagi ke halaman template tadi dan klik tombol Download sekali lagi.File-nya bakal langsung keunduh dalam format .zip.Simpan file .zip itu ke folder lokal kamu, misalnya ke: ./source-code/mahouse.zip. Udah, beres. Sekarang kamu punya satu paket desain + kode yang siap kamu eksplor. Tinggal buka, utak-atik, terus bawa ke proyek kamu. Mulai Buat Proyek React 1. Buat Folder Pertama-tama, bikin dulu folder baru sbagai tempat project-nya. Setelah itu, buka terminal di dalam folder itu, lalu ketik perintah ini: npm create vite@latest Sampai hasilnya nanti kira-kira bakal keliatan kayak gini: 2. Jalankan Proyeknya cd .\\mahouse-buildwithangga npm install npm run dev 3. Akses Browser 4. Buat Filenya Step 1 Sekarang kita geser dulu ke proyek gratisan dari shaynakit.com. Di sini kita bakal mulai utak-atik kode dari proyek mahouse yang udh kamu download. Pastikan file-nya udah kamu extract all, terus udah kamu buka juga di VSCode. Intinya, sebelum lanjut, pastiin dulu tampilan awalnya udah kayak gini: Nah, ini dia halaman utama VSCode ibaratnya kayak meja kerja kamu. Semua alat, catatan, dan bahan buat ngoding ada di sini. Di sinilah tempat kamu mulai ngerakit proyeknya pelan-pelan, satu baris kode demi satu ide. Step 2 Sekarang kita masuk ke file package.json ibaratnya ini kayak daftar belanjaan atau peta jalan buat project kamu. Nah, di dalamnya, kamu tinggal isi bagian ini: "dependencies": { ...... ...... "@tailwindcss/vite": "^4.1.4", "tailwindcss": "^4.1.4", ...... ...... }, ekarang buka file vite.config.ts anggap aja ini kayak remote control yang nge-set gimana proyek kamu jalan. Isi file ini dengan kode berikut: import { defineConfig } from 'vite' import react from '@vitejs/plugin-react-swc' import tailwindcss from '@tailwindcss/vite' // <https://vite.dev/config/> export default defineConfig({ plugins: [react(), tailwindcss(),], }) Selanjutnya, di terminal kamu tinggal jalankan perintah ini buat mulai ngejalanin proyeknya: npm install npm install itu ibarat kamu lagi masuk ke toko alat tempur buat ngoding, terus bilang, "Bro, aku butuh senjata ini buat proyek aku!" Nah, begitu kamu ketik npm install, si npm (singkatan dari Node Package Manager) bakal lngsung ngedownload semua alat-alat (alias package/library) yang dibutuhin sama proyek kamu. Biasanya perintah ini dijalanin pertama kali setelah kamu ngedownload proyek orang dari GitHub atau nyalin template. Soalnya, di situ biasanya cuma ada daftar alat-alat yang dibutuhin (di file package.json), tapi barangnya belum ada. Jadi, npm install tuh yang bakal ngambilin semuanya dari gudang dan naro di folder node_modules. Hasil akhirnya? Kamu bakal dapet folder node_modules yang isinya semua tools yang proyek kamu butuhin biar bisa jalan dengan mulus. Bisa dibilang ini tuh kayak ngisi ulang amunisi sebelum perang di medan coding. Hasil: Step 3 Selanjutnya, tinggal kamu ketik dan jalanin perintah ini di terminal: npx @tailwindcss/cli -i ./src/input.css -o ./src/output.css --watch Perintah npx @tailwindcss/cli -i ./src/input.css -o ./src/output.css --watch itu intinya kamu nyuruh Tailwind buat ambil CSS mentah lalu diolah jadi CSS siap pakai, terus dipantau terus kalau ada perubahan. Lalu akses Proyeknya ke Browser kalian. Hasil: Step 4 Abis itu, klik tombol “Explore Houses”, nanti kamu bakal langsung diarahkan ke halaman ini: Ini tuh tampilan buat mobile, alias layar kcil dengan lebar maksimal 640px. Kalau ukurannya udah lewat dari 640px, itu udah masuk ke wilayah tablet atau desktop. Jadi, intinya: di bawah 640px = mobile, di atas itu = tablet dan desktop. Focus Sekarang, kita fokus ke bagian ini ya, kita bakal ngulik dan kerja bareng di bagian Category. Ambil Kodenya File-nya bisa kamu temukan di sini: <http://127.0.0.1:3000/src/home.html> Nah, ini dia kode yang buat nampilin gambar dari path tadi: <!-- Categories Section --> <section class="relative w-full overflow-hidden"> <div class="swiper !px-6"> <div class="swiper-wrapper py-5"> <!-- City --> <a href="#" class="swiper-slide group max-w-[130px]"> <div class="flex flex-col gap-4 items-center justify-center py-5 bg-accent rounded-[16px] w-[130px] transition-all duration-300 group-hover:ring group-hover:ring-foreground" > <div class="w-[60px] h-[60px] bg-primary rounded-full flex items-center justify-center shrink-0" > <img src="./assets/images/icons/building-4.svg" /> </div> <div class="text-center"> <h3 class="font-semibold text-[16px]">City</h3> <p class="text-secondary text-[14px]">829 house</p> </div> </div> </a> <!-- Nature --> <a href="#" class="swiper-slide group max-w-[130px]"> <div class="flex flex-col gap-4 items-center justify-center py-5 bg-accent rounded-[16px] w-[130px] transition-all duration-300 group-hover:ring group-hover:ring-foreground" > <div class="w-[60px] h-[60px] bg-primary rounded-full flex items-center justify-center shrink-0" > <img src="./assets/images/icons/tree.svg" /> </div> <div class="text-center"> <h3 class="font-semibold text-[16px]">Nature</h3> <p class="text-secondary text-[14px]">54,293 house</p> </div> </div> </a> <!-- Apartment --> <a href="#" class="swiper-slide group max-w-[130px]"> <div class="flex flex-col gap-4 items-center justify-center py-5 bg-accent rounded-[16px] w-[130px] transition-all duration-300 group-hover:ring group-hover:ring-foreground" > <div class="w-[60px] h-[60px] bg-primary rounded-full flex items-center justify-center shrink-0" > <img src="./assets/images/icons/3dcube.svg" /> </div> <div class="text-center"> <h3 class="font-semibold text-[16px]">Apartment</h3> <p class="text-secondary text-[14px]">221 house</p> </div> </div> </a> </div> </div> </section> Covert Ke React Pake Website Step-stepnya : Yuk, buka link ini: <https://transform.tools/html-to-jsx> Paste kode-nya persis seperti gambar di atas ya (gambar itu hasil dari kode yang tadi kamu paste).Klik tombol “Copy” yang berwrna biru di pojok kanan atas, ya!Lalu, paste kode-nya ke proyek React kamu, kira-kira bakal jadi seperti ini: Data React Bayangin kamu dapet data dari Back End Engineer, bentuknya kayak gini nih. Entah kamu liatnya di Postman, atau di tools lain yang biasa kamu pakai buat cek data. [ { id: 1, name: "City", slug: "city", photo: "<https://cakfan.github.io/mahouse-dark-mobile/assets/images/icons/building-4.svg>", staycations_count: 3, staycations: [ { id: 101, name: "Sunset Paradise Villa", location: "Bali, Indonesia", price: 1200000, image: "<https://example.com/images/staycations/villa1.jpg>", }, { id: 102, name: "Ocean Breeze Resort", location: "Lombok, Indonesia", price: 950000, image: "<https://example.com/images/staycations/resort1.jpg>", }, { id: 103, name: "Coral View Inn", location: "Raja Ampat, Indonesia", price: 1500000, image: "<https://example.com/images/staycations/inn1.jpg>", }, ], popular_staycations: [ { id: 103, name: "Coral View Inn", location: "Raja Ampat, Indonesia", price: 1500000, image: "<https://example.com/images/staycations/inn1.jpg>", }, { id: 101, name: "Sunset Paradise Villa", location: "Bali, Indonesia", price: 1200000, image: "<https://example.com/images/staycations/villa1.jpg>", }, ], }, { id: 2, name: "Nature", slug: "nature", photo: "<https://cakfan.github.io/mahouse-dark-mobile/assets/images/icons/tree.svg>", staycations_count: 3, staycations: [ { id: 101, name: "Sunset Paradise Villa", location: "Bali, Indonesia", price: 1200000, image: "<https://example.com/images/staycations/villa1.jpg>", }, { id: 102, name: "Ocean Breeze Resort", location: "Lombok, Indonesia", price: 950000, image: "<https://example.com/images/staycations/resort1.jpg>", }, { id: 103, name: "Coral View Inn", location: "Raja Ampat, Indonesia", price: 1500000, image: "<https://example.com/images/staycations/inn1.jpg>", }, ], popular_staycations: [ { id: 103, name: "Coral View Inn", location: "Raja Ampat, Indonesia", price: 1500000, image: "<https://example.com/images/staycations/inn1.jpg>", }, { id: 101, name: "Sunset Paradise Villa", location: "Bali, Indonesia", price: 1200000, image: "<https://example.com/images/staycations/villa1.jpg>", }, ], }, { id: 3, name: "Apartment", slug: "apartment", photo: "<https://cakfan.github.io/mahouse-dark-mobile/assets/images/icons/3dcube.svg>", staycations_count: 3, staycations: [ { id: 101, name: "Sunset Paradise Villa", location: "Bali, Indonesia", price: 1200000, image: "<https://example.com/images/staycations/villa1.jpg>", }, { id: 102, name: "Ocean Breeze Resort", location: "Lombok, Indonesia", price: 950000, image: "<https://example.com/images/staycations/resort1.jpg>", }, { id: 103, name: "Coral View Inn", location: "Raja Ampat, Indonesia", price: 1500000, image: "<https://example.com/images/staycations/inn1.jpg>", }, ], popular_staycations: [ { id: 103, name: "Coral View Inn", location: "Raja Ampat, Indonesia", price: 1500000, image: "<https://example.com/images/staycations/inn1.jpg>", }, { id: 101, name: "Sunset Paradise Villa", location: "Bali, Indonesia", price: 1200000, image: "<https://example.com/images/staycations/villa1.jpg>", }, ], }, ]; Pathnya: Ambil URL path dari hasil Get Data itu, terus simpan di file .env kamu, kira-kira seperti ini: # env VITE_REACT_API_URL = <http://127.0.0.1:8000/api> VITE_REACT_API_STORAGE_URL = <http://127.0.0.1:8000/storage> VITE_REACT_API_URL ini isinya alamat lokal buat akses endpoint API React kamu lewat Vite, jadi nanti buat ngambil data dari backend langsung ke sini. VITE_REACT_API_STORAGE_URL ini isinya alamat lokal buat akses folder storage API React kamu lewat Vite, biasanya dipake buat manggil file-file yang disimpen di backend. Types (TypeScript) Kalau kita liat dari data yang dikasih, kira-kira tipe TypeScript-nya bakal jadi kayak gini: // src/types/type.ts export interface Category { id: number; name: string; slug: string; photo: string; staycations_count: number; staycations: Staycations[]; popular_staycations: Staycations[]; } Download Library Lanjut ya, sekarang kita download dulu beberapa kebutuhan. Biasanya sih, walau cuma satu halaman, tetep aja ada beberapa library yang sering dipake biar semuanya enak dijalanin. axios Axios itu ibarat kurir pribadi di proyek React TypeScript kamu. Tugasnya? Bolak-balik nganter data dari frontend ke backend dan sebaliknya. Jadi kamu tinggal bilang, “Eh, ambilin data dari API ini dong,” dan si Axios langsung gerak ambilinnya buat kamu rapih, cepat, dan bisa diatur sesuai maumu. Cocok banget buat bikin komunikasi antar sistem jadi lancar tanpa ribet. Perintahin di terminal React kamu gini: npm install axios // src/services/apiServices.ts import axios from 'axios'; const BASE_URL = import.meta.env.VITE_REACT_API_URL; const apiClient = axios.create({ baseURL: BASE_URL, }); // Export axios.isAxiosError export const isAxiosError = axios.isAxiosError; export default apiClient; Bayangin kamu lagi bangun jalur kirim barang dari toko (frontend) ke gudang (backend). Nah, apiClient ini ibarat mobil pengantar yang udah disiapin jalurnya lewat peta (si VITE_REACT_API_URL). Jadi tiap kali kamu mau kirim atau ambil barang (data), tinggal panggil mobil ini nggak usah repot setel alamatnya tiap kali. Bonusnya, kamu juga dikasih alat buat ngecek kalau ada kecelakaan di jalan: isAxiosError buat tahu itu error dari jalur Axios atau bukan. Praktis banget buat urusan bolak-balik data. // HomePage.tsx import apiClient from "../services/apiServices"; Sekarang masukin aja apiClient itu biar nanti bisa langsung kamu pake di halaman HomePage.tsx, tinggal panggil dan gas ambil datanya. react-router-dom react-router-dom itu kayak GPS-nya aplikasi React kamu. Dia yang ngatur kamu mau pindah ke halaman mana tanpa harus rfresh satu halaman penuh. Jadi pas kamu klik tombol “Profile” atau “Explore”, dia langsung ngarahin ke rute yang bener halus, cepat, dan seamless! Tanpa dia, aplikasi kamu bakal kayak rumah tanpa pintu: semua ada, tapi bingung mau masuk ke mana 😄 Perintahin di terminal React kamu gini: npm i react-router-dom // HomePage.tsx import { Link } from "react-router-dom"; Sekarang masukin aja Link itu biar nanti bisa langsung kamu pake di halaman HomePage.tsx, tinggal panggil dan gas ambil datanya. swiper Swiper itu ibarat rel geser otomatis buat elemen-elemen di halaman kamu kayak galeri foto, daftar produk, atau testimoni. Tinggal kamu kasih "gerbong"-nya (alias item-item yang mau ditampilin), si Swiper yang bantuin geserin dengan mulus. Bisa geser manual, bisa otomatis, bisa juga dikustom sesuka hati. Pokoknya, bikin komponen kamu jadi lebih interaktif dan modern banget! Perintahin di terminal React kamu gini: npm i swiper // HomePage.tsx import { Swiper, SwiperSlide } from "swiper/react"; Sekarang masukin aja swiper itu biar nanti bisa langsung kamu pake di halaman HomePage.tsx, tinggal panggil dan gas ambil datanya. Setelah Semua di Atas Bisa di Tulis Gini // HomePage.tsx import { useEffect, useState } from "react"; import { Swiper, SwiperSlide } from "swiper/react"; import type { Category } from "./types/type"; import apiClient from "../services/apiServices"; import { Link } from "react-router-dom"; // Mengambil data kategori dari API const fetchCategories = async () => { const response = await apiClient.get("/categories"); return response.data.data; }; export default function Homepage() { const [categories, setCategories] = useState<Category[]>([]); const [loadingCategories, setLoadingCategories] = useState(true); const [error, setError] = useState<string | null>(null); // Mengambil data kategori saat komponen pertama kali dimuat useEffect(() => { const fetchCategoriesData = async () => { try { const categoriesData = await fetchCategories(); setCategories(categoriesData); } catch { setError("Failed to load categories"); } finally { setLoadingCategories(false); } }; fetchCategoriesData(); }, []); // Jika masih loading atau ada error, tampilkan pesan loading atau error if (loadingCategories) { return <p>Loading categories...</p>; } if (error) { return <p>Error loading data: {error}</p>; } // Base URL untuk gambar kategori const BASE_URL = import.meta.env.VITE_REACT_API_STORAGE_URL; return ( <section className="relative w-full overflow-hidden"> ... </section> ); } Swiper Pake // HomePage.tsx <section className="relative w-full overflow-hidden"> <div className="swiper !px-6"> <Swiper className="swiper-wrapper py-5" direction="horizontal" spaceBetween={20} slidesPerView="auto" slidesOffsetAfter={20} slidesOffsetBefore={20}> <SwiperSlide className="!w-fit"> {/* City */} <a href="#" className="swiper-slide group max-w-[130px]"> <div className="flex flex-col gap-4 items-center justify-center py-5 bg-accent rounded-[16px] w-[130px] transition-all duration-300 group-hover:ring group-hover:ring-foreground"> <div className="w-[60px] h-[60px] bg-primary rounded-full flex items-center justify-center shrink-0"> <img src="./assets/images/icons/building-4.svg" /> </div> <div className="text-center"> <h3 className="font-semibold text-[16px]">City</h3> <p className="text-secondary text-[14px]">829 house</p> </div> </div> </a> </SwiperSlide> <SwiperSlide className="!w-fit"> {/* Nature */} ... </SwiperSlide> <SwiperSlide className="!w-fit"> {/* Apartment */} ... </SwiperSlide> </Swiper> </div> </section> Penjelasan Pertama, kamu perlu pastiin udah install library Swiper di proyek React kamu. Bisa lewat npm install swiper atau yarn add swiper. Setelah itu, import Swiper dan SwiperSlide dari swiper/react, plus kalau mau styling-nya langsung jalan, tambahin juga import CSS-nya import 'swiper/css';. Nah, di dalam komponennya, kamu tinggal bungkus item kamu pake <Swiper> dan tiap konten di dalamnya taruh di <SwiperSlide>. Kamu juga bisa atur properti kayak slidesPerView="auto" biar jumlah slide-nya fleksibel, dan spaceBetween buat jarak antar slide. Jangan lupa slidesOffsetBefore dan slidesOffsetAfter kalau kamu pengn ada padding di kanan-kiri. Kedua, dari sisi HTML dan styling-nya, kamu bisa bungkus Swiper dalam <section> dan <div className="swiper"> buat kontrol layout dan padding. Tiap SwiperSlide kamu bisa atur lebar pakai !w-fit, dan di dalamnya isi komponen custom seperti card kategori—misalnya ikon, nama, dan jumlah rumah. Di contoh ini, pakai kmbinasi Tailwind seperti bg-accent, rounded-[16px], dan group-hover:ring biar animasi hover-nya hidup. Hasil akhirnya adalah tampilan slider kategori yang bisa digeser kanan-kiri dengan tampilan modern, responsif, dan interaktif! Hapus // HomePage.tsx <Swiper className="swiper-wrapper py-5" direction="horizontal" spaceBetween={20} slidesPerView="auto" slidesOffsetAfter={20} slidesOffsetBefore={20}> <SwiperSlide className="!w-fit"> {/* City */} <a href="#" className="swiper-slide group max-w-[130px]"> <div className="flex flex-col gap-4 items-center justify-center py-5 bg-accent rounded-[16px] w-[130px] transition-all duration-300 group-hover:ring group-hover:ring-foreground"> <div className="w-[60px] h-[60px] bg-primary rounded-full flex items-center justify-center shrink-0"> <img src="./assets/images/icons/building-4.svg" /> </div> <div className="text-center"> <h3 className="font-semibold text-[16px]">City</h3> <p className="text-secondary text-[14px]">829 house</p> </div> </div> </a> </SwiperSlide> </Swiper> Nah, bagian “Nature” dan “Apartment” ini wajib kamu hapus dulu karena nanti kita bakal buat mereka jadi perulangan pakai React JS biar lebih efisien dan gampang diatur. Jadi, jangan pusing bikin slide satu-satu manual, nanti React yang handle. Pengkondisian Pake // HomePage.tsx {categories.length > 0 ? "SwiperSlide" : "belum ada data category terbaru"} Jadi gini, kalau categories kamu isinya ada lebih dari nol, berarti data kategori udah siap dan langsung kita tampilin pake komponen SwiperSlide biar slider-nya jalan. Tapi, kalau ternyata belum ada data sama sekali, kita kasih pesan santai: “belum ada data category terbaru” supaya user nggak bingung dan tau kalau datanya memang lagi kosong. Gampang, kan? Jadi user dapet info yang jelas, bukan tampilan kosong doang. Lalu“SwiperSlide” nya ubah isinya dengan tag SwiperSlide, hasilnya gini: // HomePage.tsx <Swiper className="swiper-wrapper py-5" direction="horizontal" spaceBetween={20} slidesPerView="auto" slidesOffsetAfter={20} slidesOffsetBefore={20}> {categories.length > 0 ? ( <SwiperSlide className="!w-fit"> {/* City */} <a href="#" className="swiper-slide group max-w-[130px]"> <div className="flex flex-col gap-4 items-center justify-center py-5 bg-accent rounded-[16px] w-[130px] transition-all duration-300 group-hover:ring group-hover:ring-foreground"> <div className="w-[60px] h-[60px] bg-primary rounded-full flex items-center justify-center shrink-0"> <img src="./assets/images/icons/building-4.svg" /> </div> <div className="text-center"> <h3 className="font-semibold text-[16px]">City</h3> <p className="text-secondary text-[14px]">829 house</p> </div> </div> </a> </SwiperSlide> ) : ( "belum ada data category terbaru" )} </Swiper> Perulangan Caranya // HomePage.tsx categories.map((category) => ("SwiperSlide kita")) Nah, bayangin kamu punya tumpukan kategori yang pengen ditampilin. Dengan categories.map((category) => ("SwiperSlide kita")), kamu kayak lagi bilang ke React, “Eh, buat dong satu slide buat tiap kategori yang ada!” Jadi, buat setiap item di daftar kategori, React bakal bikin satu SwiperSlide baru yang isiya sesuai sama data kategori itu. Praktis banget, tinggal nambah data aja, langsung muncul slide baru. Nah, supaya bener, tag <SwiperSlide> itu kamu masukin langsung ke dalam fungsi map-nya. Jadi nanti tiap kategori bakal dibungkus oleh <SwiperSlide> secara otomatis. Hasilnya bakal kayak gini: // HomePage.tsx <Swiper className="swiper-wrapper py-5" direction="horizontal" spaceBetween={20} slidesPerView="auto" slidesOffsetAfter={20} slidesOffsetBefore={20}> {categories.length > 0 ? categories.map((category) => ( <SwiperSlide key={category.id} className="!w-fit"> {/* City */} <a href="#" className="swiper-slide group max-w-[130px]"> <div className="flex flex-col gap-4 items-center justify-center py-5 bg-accent rounded-[16px] w-[130px] transition-all duration-300 group-hover:ring group-hover:ring-foreground"> <div className="w-[60px] h-[60px] bg-primary rounded-full flex items-center justify-center shrink-0"> <img src={`${BASE_URL}/${category.photo}`} /> </div> <div className="text-center"> <h3 className="font-semibold text-[16px]">{category.name}</h3> <p className="text-secondary text-[14px]">{category.staycations_count} house</p> </div> </div> </a> </SwiperSlide> )) : "belum ada data category terbaru"} </Swiper> Link Pake Selanjutnya, kita perlu ubah linknya jadi kayak gini supaya pas diklik, dia langsung bawa kamu ke halaman yang sesuai dengan kategori itu. Jadi link-nya dinamis, nggak statis lagi bikin pengalaman user makin mulus dan keren. // HomePage.tsx <SwiperSlide key={category.id} className="!w-fit"> {/* City */} <Link to={`/category/${category.slug}`} className="swiper-slide group max-w-[130px]"> <div className="flex flex-col gap-4 items-center justify-center py-5 bg-accent rounded-[16px] w-[130px] transition-all duration-300 group-hover:ring group-hover:ring-foreground"> <div className="w-[60px] h-[60px] bg-primary rounded-full flex items-center justify-center shrink-0"> <img src={`${BASE_URL}/${category.photo}`} /> </div> <div className="text-center"> <h3 className="font-semibold text-[16px]">{category.name}</h3> <p className="text-secondary text-[14px]">{category.staycations_count} house</p> </div> </div> </Link> Jadi, di sini kamu pakai tag <Link> dari react-router-dom buat bikin area yang bisa diklik dan langsung bawa user ke halaman kategori tertentu, misalnya /category/city. Jadi bukan cuma tampilan doang, tapi ada navigasi dinamis yang bikin pengalaman browsing lebih lancar tanpa reload halaman. Nah, di dalam <Link> itu ada <img> yang sumber gambarnya (src) kita bikin dinamis juga. Jadi gambarnya diambil dari backend lewat BASE_URL ditambah path foto kategori (category.photo). Ini bikin tiap slide punya gambar unik sesuai kategorinya, jadi slider-nya nggak cuma keren tapi juga informatif. Rupiah (Bonus) Caranya // HomePage.tsx // Format currency to IDR const formatCurrency = (value: number) => { return new Intl.NumberFormat("id-ID", { style: "currency", currency: "IDR", maximumFractionDigits: 0, }).format(value); }; Fungsi formatCurrency ini tugasnya buat ngerapihin angka jadi format mata uang Indonesia, alias Rupiah. Jadi misalnya kamu punya angka 1500000, nanti bakal diubah jadi Rp1.500.000 yang enak dilihat dan gampang dimengerti. Gak ada desimal, biar tampilannya simpel dan rapi. Kamu tinggal panggil fungsi ini kapan pun mau nampilin harga atau uang di UI biar tampilannya profesional. // HomePage.tsx <strong>{formatCurrency(staycation.price)}</strong> Nah, di bagian ini kamu panggil fungsi formatCurrency buat ngerubah staycation.price jadi format Rupiah yang cakep. Jadi harga staycation-nya gak cuma angka doang, tapi lagsung tampil tebal dan rapi kayak Rp1.500.000 gitu. Hasil Akhir Semua Tutorial Kita Jadi, ini dia hasil akhir proyeknya! Sebenarnya kamu bisa langsung copy-paste kode ini kalau mau, atau kalau kamu lagi ngecek-ngecek, “Eh, udah sama belum ya sama yang aku buat?” tinggal cocok-cocokin aja. Santai, tinggal pakai dan eksplorasi lebih lanjut sesuai kebutuhan kamu. import { useEffect, useState } from "react"; import { Swiper, SwiperSlide } from "swiper/react"; import type { Category } from "./types/type"; import apiClient from "../services/apiServices"; import { Link } from "react-router-dom"; // Mengambil data kategori dari API const fetchCategories = async () => { const response = await apiClient.get("/categories"); return response.data.data; }; export default function Homepage() { const [categories, setCategories] = useState<Category[]>([]); const [loadingCategories, setLoadingCategories] = useState(true); const [error, setError] = useState<string | null>(null); // Mengambil data kategori saat komponen pertama kali dimuat useEffect(() => { const fetchCategoriesData = async () => { try { const categoriesData = await fetchCategories(); setCategories(categoriesData); } catch { setError("Failed to load categories"); } finally { setLoadingCategories(false); } }; fetchCategoriesData(); }, []); // Jika masih loading atau ada error, tampilkan pesan loading atau error if (loadingCategories) { return <p>Loading categories...</p>; } if (error) { return <p>Error loading data: {error}</p>; } // Base URL untuk gambar kategori const BASE_URL = import.meta.env.VITE_REACT_API_STORAGE_URL; return ( <section className="relative w-full overflow-hidden"> <div className="swiper !px-6"> <Swiper className="swiper-wrapper py-5" direction="horizontal" spaceBetween={20} slidesPerView="auto" slidesOffsetAfter={20} slidesOffsetBefore={20}> {categories.length > 0 ? categories.map((category) => ( <SwiperSlide key={category.id} className="!w-fit"> {/* City */} <Link to={`/category/${category.slug}`} className="swiper-slide group max-w-[130px]"> <div className="flex flex-col gap-4 items-center justify-center py-5 bg-accent rounded-[16px] w-[130px] transition-all duration-300 group-hover:ring group-hover:ring-foreground"> <div className="w-[60px] h-[60px] bg-primary rounded-full flex items-center justify-center shrink-0"> <img src={`${BASE_URL}/${category.photo}`} /> </div> <div className="text-center"> <h3 className="font-semibold text-[16px]">{category.name}</h3> <p className="text-secondary text-[14px]">{category.staycations_count} house</p> </div> </div> </Link> </SwiperSlide> )) : "belum ada data category terbaru"} </Swiper> </div> </section> ); } Penutup Sip, itu dia semuaya! Semoga langkah-langkahnya jelas dan kamu makin pede buat ngulik React + TypeScript. Kalau ada yng bingung atau pengen tanya-tanya, jangan ragu buat beli kelas premium BuildWithAngga, ya! Selamat ngoding, semoga proyek kamu lancar jaya dan hasilnya keren.