Studi Kasus #13: Aplikasi Wisata Indonesia – Membuat Halaman Beranda (Daftar Wisata)
Halo, calon full-stack developer!
Di Studi Kasus #12, kita berhasil menghubungkan frontend dengan backend dan mengambil data dari API, meski baru sebatas menampilkannya di console. Sekarang saatnya kita menampilkan data tersebut secara visual di halaman beranda. Kita akan membuat halaman daftar tempat wisata yang menampilkan setiap destinasi dalam bentuk kartu (card) yang menarik.
Fitur yang akan dibuat:
- Mengambil data destinasi dari endpoint
/api/destinationsmenggunakan service yang sudah dibuat. - Menampilkan loading state saat data sedang diambil.
- Menampilkan error state jika terjadi kesalahan.
- Menampilkan daftar destinasi dalam bentuk grid kartu.
- Setiap kartu berisi: foto cover, nama, lokasi, dan deskripsi singkat.
- Menambahkan CSS sederhana agar tampilan rapi.
Mari kita mulai!
Langkah 1: Menyiapkan Service Destinasi
Kita sudah membuat src/services/destinationService.js di tutorial sebelumnya. Pastikan isinya seperti ini:
import API from './api';
export const getDestinations = async () => {
try {
const response = await API.get('/destinations');
return response.data;
} catch (error) {
console.error('Gagal mengambil destinasi:', error);
throw error;
}
};
export const getDestinationById = async (id) => {
try {
const response = await API.get(`/destinations/${id}`);
return response.data;
} catch (error) {
console.error('Gagal mengambil destinasi:', error);
throw error;
}
};
Jika belum, buat file tersebut.
Langkah 2: Membuat Komponen Card Destinasi
Kita akan membuat komponen khusus untuk menampilkan satu kartu destinasi. Buat file src/components/DestinationCard.jsx:
import React from 'react';
import './DestinationCard.css'; // kita akan buat CSS nya nanti
const DestinationCard = ({ destination }) => {
// Base URL untuk gambar (backend menyajikan di /uploads)
const imageUrl = destination.cover_image
? `http://localhost:3000/uploads/${destination.cover_image}`
: 'https://via.placeholder.com/300x200?text=No+Image';
// Potong deskripsi jika terlalu panjang
const shortDescription = destination.description
? destination.description.substring(0, 100) + '...'
: 'Tidak ada deskripsi';
return (
<div className="destination-card">
<img src={imageUrl} alt={destination.name} className="card-image" />
<div className="card-content">
<h3>{destination.name}</h3>
<p className="location">📍 {destination.location}</p>
<p className="description">{shortDescription}</p>
</div>
</div>
);
};
export default DestinationCard;
Penjelasan:
- Komponen menerima prop
destinationberisi data satu destinasi. imageUrldibentuk dari URL backend. Jika tidak ada cover, gunakan placeholder.- Deskripsi dipotong agar tidak terlalu panjang di card.
Sekarang buat file CSS src/components/DestinationCard.css:
.destination-card {
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.2s;
background: white;
}
.destination-card:hover {
transform: scale(1.02);
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
.card-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.card-content {
padding: 15px;
}
.card-content h3 {
margin: 0 0 10px 0;
color: #333;
}
.location {
color: #666;
font-size: 0.9em;
margin: 5px 0;
}
.description {
color: #777;
font-size: 0.9em;
line-height: 1.4;
}
Langkah 3: Membuat Halaman Beranda (Home)
Sekarang kita ubah src/pages/Home.jsx untuk menampilkan daftar destinasi. Gunakan state untuk menyimpan data, loading, dan error.
import React, { useState, useEffect } from 'react';
import { getDestinations } from '../services/destinationService';
import DestinationCard from '../components/DestinationCard';
import './Home.css'; // CSS untuk halaman
const Home = () => {
const [destinations, setDestinations] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchDestinations = async () => {
try {
const data = await getDestinations();
setDestinations(data);
} catch (err) {
setError('Gagal mengambil data wisata. Silakan coba lagi.');
} finally {
setLoading(false);
}
};
fetchDestinations();
}, []);
if (loading) {
return <div className="loading">Memuat data...</div>;
}
if (error) {
return <div className="error">{error}</div>;
}
return (
<div className="home">
<h1>Destinasi Wisata Indonesia</h1>
{destinations.length === 0 ? (
<p>Belum ada destinasi wisata.</p>
) : (
<div className="destination-grid">
{destinations.map((dest) => (
<DestinationCard key={dest.id} destination={dest} />
))}
</div>
)}
</div>
);
};
export default Home;
Penjelasan:
useStateuntuk menyimpan data, loading, dan error.useEffectmemanggil API saat komponen dimuat.- Menampilkan loading atau error jika diperlukan.
- Jika data ada, kita render grid dengan memetakan setiap destinasi ke komponen
DestinationCard.
Buat file CSS src/pages/Home.css:
.home {
padding: 20px;
}
.home h1 {
text-align: center;
margin-bottom: 30px;
color: #2c3e50;
}
.destination-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.loading, .error {
text-align: center;
margin-top: 50px;
font-size: 1.2em;
}
.error {
color: #e74c3c;
}
Langkah 4: Menyesuaikan URL Gambar di Environment
URL backend saat ini hardcode http://localhost:3000. Sebaiknya kita simpan di environment variable agar mudah diganti saat production. Buat file .env di root folder frontend:
VITE_API_URL=http://localhost:3000/api
VITE_UPLOAD_URL=http://localhost:3000/uploads
Kemudian di src/services/api.js, gunakan:
const API = axios.create({
baseURL: import.meta.env.VITE_API_URL,
});
Di komponen DestinationCard, kita bisa gunakan:
const imageUrl = destination.cover_image
? `${import.meta.env.VITE_UPLOAD_URL}/${destination.cover_image}`
: 'https://via.placeholder.com/300x200?text=No+Image';
Jangan lupa untuk merestart dev server setelah menambah .env.
Langkah 5: Menguji Halaman Beranda
Pastikan backend berjalan. Jalankan frontend dengan npm run dev. Buka browser ke http://localhost:5173. Seharusnya halaman beranda menampilkan daftar destinasi dalam bentuk kartu yang rapi. Jika belum ada data, pastikan backend sudah memiliki data destinasi (bisa ditambahkan melalui Postman).
Jika terjadi error, buka console browser untuk melihat pesan error. Pastikan CORS sudah diatur di backend dan URL environment benar.
backend/uploads dan apakah URL sudah benar. Kamu bisa langsung mengakses gambar via browser untuk mengetes.
Langkah 6: Menambahkan Link ke Halaman Detail
Nantinya, setiap kartu akan bisa diklik menuju halaman detail destinasi. Untuk itu, kita bungkus kartu dengan Link dari react-router-dom. Ubah DestinationCard.jsx:
import { Link } from 'react-router-dom';
// ... di dalam return
<Link to={`/destination/${destination.id}`} className="card-link">
<div className="destination-card">
{/* ... isi kartu ... */}
</div>
</Link>
Tambahkan CSS untuk menghilangkan underline dan mempertahankan warna:
.card-link {
text-decoration: none;
color: inherit;
}
Sekarang kartu dapat diklik menuju halaman detail (yang akan kita buat di tutorial selanjutnya).
Penanganan Error dan Loading
Kita sudah menambahkan state loading dan error. Ini penting untuk pengalaman pengguna yang baik. Jangan lupa untuk menguji dengan mematikan backend sejenak untuk melihat tampilan error.
Langkah Selanjutnya
Di Studi Kasus #14, kita akan membuat halaman detail destinasi yang menampilkan informasi lengkap: galeri foto, fasilitas, dan paket harga. Kita juga akan menggunakan parameter ID dari URL.
Pastikan halaman beranda sudah berfungsi dengan baik. Jika ada kendala, periksa kembali kode dan pastikan API merespons dengan benar.
Sampai jumpa di tutorial berikutnya! 👋😊
Ditulis dengan ❤️ untuk para pengembang yang ingin membangun aplikasi wisata.