Products and Features
- Membuka Akses Ping pada IP Public VM Cloud Raya
- Maximize Your Storage Raya Access Speed with Content Delivery Network (CDN)
- How to Create Project Tag in Cloud Raya for More Organized VM Billing Report
- Exporting Cloud Raya VM to outer Cloud Raya's Infrastructure using Acronis Cyber Protect
- SSO Management on Cloud Raya
- Easy Steps to Enable VPC in Cloud Raya
- Using the SSH key Feature in Cloud Raya Dashboard
- Cloud Raya Load Balancer, Solution to Distribute Load Equally
- Create your own VPN server with DNS-Level AdBlocker using PiVPN & PiHole in CloudRaya
- Fix Broken LetsEncrypt SSL Certificate due to Expired Root CA Certificate
- Storage Raya - Cloud Raya S3 Object Storage
- How to Make a Snapshot and Configure VM Backup in Cloud Raya
- How to Request Services or Licenses Products
- Adding, Attaching, and Resize Root Storage Disk in Cloud Raya VPS
- Managing your DNS Zone with DNS Bucket in Cloud Raya
- Create VM, Custom Package, Reinstall VM, and Adjusting Security Profile
- How to backup Linux VM via Acronis in Cloud Raya
- How to Backup Desktop Linux and Windows via Acronis in Cloud Raya
- Backing-Up Cloud Raya Windows VM Using Acronis Cyber Protect
- Load Balancing in Cloud Raya
- Establishing a VPN in Cloud Raya
- Generating an API Token
- Generating an API key
- Deploying a Virtual Machine in Cloud Raya
- Show Remaining Articles ( 9 ) Collapse Articles
- How to backup Linux VM via Acronis in Cloud Raya
- How to Backup Desktop Linux and Windows via Acronis in Cloud Raya
- Integrasi S3 Storage Raya dan Strapi untuk Pengoptimalan Penyimpanan Aset - Bag. 4
- Maximize Your Storage Raya Access Speed with Content Delivery Network (CDN)
- Managing Storage Raya from various tools and from various OS
- Binding NextCloud with Cloud Raya S3 Object Storage as External Storage Mount
- Storage Raya - Cloud Raya S3 Object Storage
- Deploy Cpanel Menggunakan Ubuntu 20.04 di Cloudraya - Bagian 1
- Exporting Cloud Raya VM to outer Cloud Raya's Infrastructure using Acronis Cyber Protect
- Using the SSH key Feature in Cloud Raya Dashboard
- Adding, Attaching, and Resize Root Storage Disk in Cloud Raya VPS
- Create VM, Custom Package, Reinstall VM, and Adjusting Security Profile
- How to backup Linux VM via Acronis in Cloud Raya
- Backing-Up Cloud Raya Windows VM Using Acronis Cyber Protect
- Deploying a Virtual Machine in Cloud Raya
Integration
- Integrasi S3 Storage Raya dan Strapi untuk Pengoptimalan Penyimpanan Aset - Bag. 4
- Integrasi Konten Strapi ke dalam Frontend React - Bag. 3
- Mengelola Konten dalam Strapi Headless CMS - Bag. 2
- Menginstal Strapi Headless CMS di Cloud Raya - Bag. 1
- Panduan Menggunakan SSH Key pada VM Cloud Raya dengan PuTTY
- Menginstal Beberapa Versi PHP dalam Satu VM untuk Pengembangan Web yang Lebih Fleksibel
- Replatforming Apps to K8s with RKE and GitLab CI
- Integrasi API OpenAI: Completions dalam PHP
- Membangun Email Server di Cloud Raya Menggunakan iRedMail
- Meningkatkan Pengiriman Email dengan Sendinblue SMTP Relay
- Bangun Self Hosted Password Manager menggunakan Passbolt
- How to Install Podman on Almalinux/Rocky Linux 9
- ElkarBackup: GUI Based backup Tools based on Rsync and Rsnapshot
- Meningkatkan Kinerja Webserver dengan SSL Termination pada NGINX Load Balancer
- Menggunakan NGINX sebagai HTTP Load Balancer
- Otomatisasi Task dengan Cronjob
- Upgrade Zimbra and the OS Version
- Deploy Mailu on Rancher Kubernetes
- Export dan Import Database di MySQL atau MariaDB Menggunakan Mysqldump
- Backup & Sync Local and Remote Directories Using RSYNC
- Managing Storage Raya from various tools and from various OS
- Binding NextCloud with Cloud Raya S3 Object Storage as External Storage Mount
- Simple monitoring and alerting with Monit on Ubuntu 22.04 LTS
- VS Code on your browser! How to install code-server on a VM
- Implementing Redis HA and Auto-Failover on Cloud Raya
- Using XFCE Desktop Environment on Cloud Raya VM
- Installing Python 3.7-3.9 on Ubuntu 22.04 Jammy LTS using PPA
- Implementing Continuous Integration with Gitlab CI and Continuous Delivery with Rancher Fleet
- Using Collabora Online on Cloud Raya NextCloud's VM
- Installing NextCloud in Cloud Raya- Detail Steps from the Beginning to the Very End
- Set Up High Availability PostgreSQL Cluster Using Patroni on Cloud Raya
- Set Up WAF KEMP in Cloud Raya Part 2
- Set Up WAF KEMP in Cloud Raya Part 1
- Using the SSH key Feature in Cloud Raya Dashboard
- Monitor Your Services Uptime Using Uptime Kuma
- Hosting Static Website with Hugo on Cloud Raya
- Kubernetes Ingress Controller using SSL in CloudRaya
- Reverse Proxy management using Nginx Proxy Manager
- Create your own VPN server with DNS-Level AdBlocker using PiVPN & PiHole in CloudRaya
- How to deploy Portainer on Linux to easily manage your docker containers
- High Availability Kubernetes Using RKE in Cloud Raya Part 3
- High Availability Kubernetes Using RKE in Cloud Raya Part 2
- High Availability Kubernetes Using RKE in Cloud Raya Part 1
- How to backup Linux VM via Acronis in Cloud Raya
- How to Backup Desktop Linux and Windows via Acronis in Cloud Raya
- Deploying Magento on Cloud Raya
- How to Install Nextcloud on Cloud Raya
- How to Install CWP in Cloud Raya
- How to Install Node.js and Launch Your First Node App
- How to install and secure MariaDB on Ubuntu 18.04 and 20.04 on Cloud Raya
- How to Install and Securing MongoDB on Ubuntu 18.04 and 20.04
- Classes: Post Installation on Ansible
- Classes: Install and Configure Ansible
- Classes: Introduction to Ansible for a robust Configuration Management
- How to Setup Active Directory Domain Service & DNS with Cloud Raya
- How to Host Your Own Docker Hub in Cloud Raya
- How to Setup Your Own Laravel with Nginx in Ubuntu 18.04
- How to Deploy Container in Cloud Raya using Docker
- Securing CentOS with iptables
- Install and Configure Squid Proxy in Ubuntu
- Installing Apache and Tomcat: A Quick Way
- Securing Ubuntu with UFW
- Install a Node.js and Launch a Node App on Ubuntu 18.04
- Installing LAMP in Ubuntu
- Installing LEMP Stack on Ubuntu 18.04
- Show Remaining Articles ( 50 ) Collapse Articles
- Articles coming soon
- Deploy Cpanel Menggunakan Ubuntu 20.04 di Cloudraya - Bagian 1
- Integrasi S3 Storage Raya dan Strapi untuk Pengoptimalan Penyimpanan Aset - Bag. 4
- Integrasi Konten Strapi ke dalam Frontend React - Bag. 3
- Mengelola Konten dalam Strapi Headless CMS - Bag. 2
- Menginstal Strapi Headless CMS di Cloud Raya - Bag. 1
- Panduan Menggunakan SSH Key pada VM Cloud Raya dengan PuTTY
- Membangun Email Server di Cloud Raya Menggunakan iRedMail
- Meningkatkan Pengiriman Email dengan Sendinblue SMTP Relay
- Bangun Self Hosted Password Manager menggunakan Passbolt
- ElkarBackup: GUI Based backup Tools based on Rsync and Rsnapshot
- Meningkatkan Kinerja Webserver dengan SSL Termination pada NGINX Load Balancer
- Menggunakan NGINX sebagai HTTP Load Balancer
- Upgrade Zimbra and the OS Version
- Deploy Mailu on Rancher Kubernetes
- Managing Storage Raya from various tools and from various OS
- Binding NextCloud with Cloud Raya S3 Object Storage as External Storage Mount
- Simple monitoring and alerting with Monit on Ubuntu 22.04 LTS
- VS Code on your browser! How to install code-server on a VM
- Implementing Redis HA and Auto-Failover on Cloud Raya
- Using XFCE Desktop Environment on Cloud Raya VM
- Implementing Continuous Integration with Gitlab CI and Continuous Delivery with Rancher Fleet
- Using Collabora Online on Cloud Raya NextCloud's VM
- Installing NextCloud in Cloud Raya- Detail Steps from the Beginning to the Very End
- Set Up WAF KEMP in Cloud Raya Part 2
- Set Up WAF KEMP in Cloud Raya Part 1
- Monitor Your Services Uptime Using Uptime Kuma
- Create your own VPN server with DNS-Level AdBlocker using PiVPN & PiHole in CloudRaya
- How to deploy Portainer on Linux to easily manage your docker containers
- High Availability Kubernetes Using RKE in Cloud Raya Part 3
- High Availability Kubernetes Using RKE in Cloud Raya Part 2
- High Availability Kubernetes Using RKE in Cloud Raya Part 1
- How to Install Nextcloud on Cloud Raya
- Classes: Post Installation on Ansible
- Classes: Install and Configure Ansible
- Classes: Introduction to Ansible for a robust Configuration Management
- Connect Windows Active Directory on Cloud Raya with Azure AD
- How to Host Your Own Docker Hub in Cloud Raya
- How to Deploy Container in Cloud Raya using Docker
- Show Remaining Articles ( 23 ) Collapse Articles
- Integrasi S3 Storage Raya dan Strapi untuk Pengoptimalan Penyimpanan Aset - Bag. 4
- Integrasi Konten Strapi ke dalam Frontend React - Bag. 3
- Mengelola Konten dalam Strapi Headless CMS - Bag. 2
- Menginstal Strapi Headless CMS di Cloud Raya - Bag. 1
- Integrasi API OpenAI untuk Membangun Chatbot Interaktif dalam Proyek PHP
- Menginstal Beberapa Versi PHP dalam Satu VM untuk Pengembangan Web yang Lebih Fleksibel
- Integrasi API OpenAI: Completions dalam PHP
- Meningkatkan Kinerja Webserver dengan SSL Termination pada NGINX Load Balancer
- Menggunakan NGINX sebagai HTTP Load Balancer
- Otomatisasi Task dengan Cronjob
- How to Deploy Django App on Cloud Raya VM Using Gunicorn, Supervisor, and Nginx
- How to Install Node.js and Launch Your First Node App
- How to Setup Your Own Laravel with Nginx in Ubuntu 18.04
- Install a Node.js and Launch a Node App on Ubuntu 18.04
- Panduan Menggunakan SSH Key pada VM Cloud Raya dengan PuTTY
- Bangun Self Hosted Password Manager menggunakan Passbolt
- Meningkatkan Kinerja Webserver dengan SSL Termination pada NGINX Load Balancer
- Export dan Import Database di MySQL atau MariaDB Menggunakan Mysqldump
- Backup & Sync Local and Remote Directories Using RSYNC
- How to Deploy Django App on Cloud Raya VM Using Gunicorn, Supervisor, and Nginx
- Set Up WAF KEMP in Cloud Raya Part 2
- Set Up WAF KEMP in Cloud Raya Part 1
- Using the SSH key Feature in Cloud Raya Dashboard
- How to backup Linux VM via Acronis in Cloud Raya
- How to Backup Desktop Linux and Windows via Acronis in Cloud Raya
- Securing CentOS with iptables
- Securing Ubuntu with UFW
- Deploy Cpanel Menggunakan Ubuntu 20.04 di Cloudraya - Bagian 1
- Integrasi S3 Storage Raya dan Strapi untuk Pengoptimalan Penyimpanan Aset - Bag. 4
- Integrasi Konten Strapi ke dalam Frontend React - Bag. 3
- Mengelola Konten dalam Strapi Headless CMS - Bag. 2
- Menginstal Strapi Headless CMS di Cloud Raya - Bag. 1
- Integrasi API OpenAI untuk Membangun Chatbot Interaktif dalam Proyek PHP
- Menginstal Beberapa Versi PHP dalam Satu VM untuk Pengembangan Web yang Lebih Fleksibel
- Membangun Email Server di Cloud Raya Menggunakan iRedMail
- Bangun Self Hosted Password Manager menggunakan Passbolt
- Meningkatkan Kinerja Webserver dengan SSL Termination pada NGINX Load Balancer
- Menggunakan NGINX sebagai HTTP Load Balancer
- Installing Python 3.7-3.9 on Ubuntu 22.04 Jammy LTS using PPA
- Reverse Proxy management using Nginx Proxy Manager
- Install and Configure Squid Proxy in Ubuntu
- Installing Apache and Tomcat: A Quick Way
- Installing LAMP in Ubuntu
- Installing LEMP Stack on Ubuntu 18.04
- Show Remaining Articles ( 2 ) Collapse Articles
- Integrasi S3 Storage Raya dan Strapi untuk Pengoptimalan Penyimpanan Aset - Bag. 4
- Integrasi Konten Strapi ke dalam Frontend React - Bag. 3
- Mengelola Konten dalam Strapi Headless CMS - Bag. 2
- Menginstal Strapi Headless CMS di Cloud Raya - Bag. 1
- Integrasi API OpenAI untuk Membangun Chatbot Interaktif dalam Proyek PHP
- Menginstal Beberapa Versi PHP dalam Satu VM untuk Pengembangan Web yang Lebih Fleksibel
- Integrasi API OpenAI: Completions dalam PHP
- Hosting Static Website with Hugo on Cloud Raya
- Deploying Magento on Cloud Raya
- How to Install CWP in Cloud Raya
- How to Setup Active Directory Domain Service & DNS with Cloud Raya
Integrasi Konten Strapi ke dalam Frontend React – Bag. 3
Setelah pada tutorial sebelumnya kita memahami cara mengelola data konten di backend Strapi, langkah selanjutnya adalah menampilkan data ini di bagian frontend aplikasi kita.
Untuk bagian frontend, akan digunakan React dengan Vite sebagai alat pengembangannya, dan untuk styling setiap elemennya, akan digunakan class dari Tailwind CSS. Pemilihan ini bertujuan untuk memudahkan dan membuat tampilan situs web kita lebih fleksibel.
Bagi Anda yang mungkin menggunakan frontend lain, pastinya tidak menjadi masalah. Namun, dalam tutorial ini disarankan untuk mengikuti langkah-langkah yang diberikan, agar Anda memahami konsepnya terlebih dahulu. Setelah itu, Anda dapat mengaplikasikan konsep ini ke teknologi lain yang Anda gunakan.
Beberapa aktifitas pengolahan data dari Strapi yang akan kita lakukan dalam frontend ini antara lain:
- Menampilkan data semua artikel
- Menampilkan data salah satu artikel berdasarkan ID
- Implementasi autentikasi melalui proses registrasi, login, dan logout
Tentunya, Anda dapat menjelajahi lebih jauh dari studi kasus sederhana ini sesuai dengan kebutuhan masing-masing di masa mendatang.
Menjalankan Proyek Vite
Jika Anda belum memiliki proyek Vite, Anda perlu membuatnya terlebih dahulu. Beruntungnya, Tailwind CSS menyediakan panduan langkah demi langkah tentang cara mengintegrasikannya ke dalam proyek Vite. Anda bisa melihat langkah-langkahnya di halaman berikut: https://tailwindcss.com/docs/guides/vite
Namun, di sini saya telah menyediakan repositori di GitHub saya terkait template website “Mini Article” yang akan kita gunakan di dalam tutorial ini.
Hal ini sejalan dengan konsep pengelolaan konten dalam Strapi yang telah dijelaskan dalam tutorial sebelumnya, bahwa kita akan membangun sebuah situs Artikel.
Akses tautan berikut dan lakukan “clone” pada repositori tersebut.
Sekarang kita sudah bisa mulai menjalankan proyek Vite dengan perintah berikut:
npm install
npm run dev -- --host 10.20.0.44 <-- [IP Private VM kita]
Pertama-tama mari kita panggil kembali endpoint API dari artikel kita untuk memastikan bahwa data dapat diakses dengan normal.
GET http://163.53.195.233:1337/api/articles?populate=*
Terpantau hasil respon API menunjukkan bahwa data telah berhasil diambil dengan sukses.
Mengambil Data dari Strapi ke dalam Proyek Vite
Pertama-tama, kita memerlukan fungsi untuk mengambil data dari Strapi. Dalam konteks ini, kita akan menggunakan sebuah hook. Hook ini akan mengatur proses pengambilan data dari API.
Buat sebuah folder bernama ‘hooks’ di dalam direktori proyek Anda, lalu buatlah sebuah hook baru dengan nama semisal 'useFetch.js'
.
Kemudian masukkan skrip seperti di bawah:
// useFetch.js
import { useEffect, useState } from 'react'; // Import useEffect and useState hooks dari React's library
// Deklarasikan useFetch sebagai custom React hook untuk mengambil data dari Endpoint URL Strapi
// Parameter URL akan diteruskan dari komponen/page yang menggunakan hook ini
const useFetch = (url) => {
const [data, setData] = useState(null); // State untuk menyimpan data yang diambil
const [error, setError] = useState(null); // State untuk menangani error
const [loading, setLoading] = useState(true); // State untuk mengatur status loading
// Definisikan fungsi fetchData untuk mengambil data menggunakan fetch
const fetchData = async () => {
setLoading(true); // Tunjukkan bahwa pengambilan data sedang berlangsung
try {
const response = await fetch(url); // Mengambil data dari URL menggunakan fetch.
const json = await response.json(); // Mengubah respons menjadi objek JSON.
setData(json); // Mengatur data dengan objek JSON yang diterima.
setLoading(false); // Menghentikan status loading.
} catch (error) {
setError(error); // Tangani kesalahan dengan mengatur state error
setLoading(false); // Tunjukkan pengambilan data sudah selesai (meskipun ada kesalahan)
}
};
// Gunakan hook useEffect untuk memulai operasi pengambilan Data ketika ada URL yg berubah
// Jalankan efek setiap kali ada permintaan baru terkait pengambilan data melalui parameter url
useEffect(() => {
fetchData(); // Panggil fungsi fetchData untuk menjalankan isi dari fungsinya
}, [url]);
// Kirimkan status loading, error, dan data ke page/komponen yang menggunakan hook ini
return { loading, error, data };
};
export default useFetch;
Di dalam skrip tersebut, kita membuat fungsi React hook kustom bernama useFetch
. Hook ini akan berguna dalam proses pengambilan data berdasarkan parameter URL Endpoint-nya. Untuk melakukannya kita menggunakan dua hook React yang penting, yaitu useState
and useEffect
:
useEffect
digunakan untuk memantau perubahan pada nilai-nilai tertentu, seperti perubahan URL dalam konteks pengambilan data. Yang memungkinkan kita menjalankan kode ketika terjadi perubahan yang kita inginkan, seperti permintaan data baru.- Sedangkan
useState
, dalam konteks pengambilan data dari URL, akan membantu kita menyimpan hasil permintaan data, status loading, dan penanganan kesalahan.
Dan kita menggunakan fetch
untuk melakukan permintaan HTTP, termasuk pengambilan data dari URL.
Dalam hook ini, terdapat fungsi asinkron yang disebut fetchData
yang mengurus seluruh proses pengambilan data. Dengan penggunaan async
, kita memanfaatkan await
untuk memastikan agar fungsi menunggu respons dari URL dahulu. Sambil terus menunjukkan loading state jika data belum didapat.
Di dalam Vite, terdapat 'App.jsx'
dimana merupakan file utama atau entri dari aplikasi React.
Pada dasarnya, file ini merupakan titik awal dari aplikasi React. Di dalamnya, kita dapat mengatur komponen-komponen utama, konfigurasi, dan struktur dasar dari aplikasi React, salah satu dari beberapa hal yang biasanya diatur dalam 'App.jsx'
adalah Routing melalui bantuan library routing seperti react-router-dom
.
Kita akan mengambil konten data artikel dari Strapi melalui file 'App.jsx'
. Modifikasi beberapa skrip pada halaman ini untuk melakukan pemanggilan data dari API Strapi, kemudian kirimkan data tersebut ke beberapa halaman yang kita inginkan:
// App.jsx
import { HomePage, ArticleContentPage, UserRegistrationPage, UserLoginPage, CategoriesPage } from "./pages";
import { Routes, Route } from "react-router-dom";
import useFetch from "./hooks/useFetch"; // Pertama, impor 'useFetch' hook ke dalam komponen App kita
export default function App() {
// Dalam fungsi useFetch, kita mendefinisikan parameter URL sesuai dengan endpoint Strapi yang diinginkan.
// Di sini, saya memuat semua konten Artikel.
let { loading: articleLoading, data: articleData, error: articleError } = useFetch('http://163.53.195.233:1337/api/articles?populate=*');
// Tampilkan animasi "Loading.." jika data masih dimuat
if (articleLoading) {
return (
<div className="fixed inset-0 flex items-center justify-center z-50 bg-opacity-50 bg-gray-800">
<div className="animate-spin rounded-full h-16 w-16 border-t-4 border-blue-500 border-opacity-75"></div>
</div>
);
}
// Tampilkan pesan "Error!" jika terjadi kesalahan dalam pengambilan data
if (articleError) {
return (
<div className="fixed inset-0 flex items-center justify-center z-50 bg-opacity-50 bg-gray-800">
<div className="bg-white p-6 rounded-md shadow-md"><p className="ml-2 text-red-500">⛔ An error occurred. Please try again later. ⛔</p></div>
</div>
);
}
// Setelah data tersedia, lewatkan nilai data artikel, yang diberi alias sebagai articleData,
// ke halaman-halaman lain yang memerlukan data ini.
// Saya akan menggunakan variabel "articles" untuk menyimpan data ini,
// yang kemudian akan kita panggil di halaman-hal yang sesuai (HomePage dan ArticleContentPage).
return (
<div>
<Routes>
<Route path="/" element={<HomePage articles={articleData} />} />
<Route path="/article/:idFromURL" element={<ArticleContentPage articles={articleData} />} />
<Route path="/registration" element={<UserRegistrationPage />} />
<Route path="/login" element={<UserLoginPage />} />
<Route path="/categories" element={<CategoriesPage />} />
</Routes>
</div>
)
}
Menampilkan Data semua Artikel dari Strapi
Sekarang, mari kita buka file 'HomePage.jsx'
di mana kita akan memanggil komponen Articles. Komponen ini akan menampilkan kumpulan data artikel yang telah kita peroleh sebelumnya. Tambahkan beberapa skrip pada halaman ini sesuai yang sudah saya highlight:
// HomePage.jsx
import React from 'react';
import { Navbar, Articles } from "../components";
// Komponen HomePage menerima prop articles yang akan digunakan untuk menampilkan daftar artikel.
// Prop articles diteruskan dari komponen App.jsx yang mengambil data artikel melalui useFetch hook.
const HomePage = ({ articles }) => {
return (
<div>
<Navbar />
{/* Mengirimkan prop articles ke komponen Articles untuk ditampilkan */}
<Articles articles={articles} />
</div>
);
};
export default HomePage;
Sekarang, mari kita membuka komponen 'Articles.jsx'
. Sesuaikan beberapa skrip menjadi seperti yang saya highlight di sini:
// Articles.jsx
import React from 'react'
import { Link } from 'react-router-dom'
import { ReactMarkdown } from 'react-markdown/lib/react-markdown'
// Menggunakan prop articles yang diterima dari parent component (App.jsx).
// Hal ini memastikan bahwa aliran data tetap terjaga
const Articles = ({articles}) => {
// data artikel awal yang masih static
/* const articles=[ ... ]*/
return (
<div className='w-full bg-[#f9f9f9] py-[50px]'>
<div className='max-w-[1240px] mx-auto'>
<div className='grid lg:grid-cols-3 md:grid-cols-2 sm:grid-cols-2 ss:grid-cols-1 gap-8 px-4 text-black'>
{/* Kita melakukan mapping terhadap data artikel yang diberikan melalui prop articles */}
{/* Menggunakan .map untuk mengiterasi setiap artikel dalam array */}
{articles.data.map((articlesmap)=>
<div key={articlesmap.id} className='bg-white rounded-xl overflow-hidden drop-shadow-md'>
<Link to={`/article/${articlesmap.id}`}>
<img
className='h-56 w-full object-cover'
src={`http://163.53.195.233:1337${articlesmap.attributes.coverImage.data.attributes.url}`}
alt={articlesmap.attributes.title} />
</Link>
<div className='p-8'>
<Link to={`/article/${articlesmap.id}`}>
<h3 className='font-bold text-2xl my-1'>{articlesmap.attributes.title}</h3>
</Link>
<div className='flex space-x-2 mt-2'>
{articlesmap.attributes.categories.data.map((category) => (
<span
key={category.id}
className='px-2 py-1 text-sm font-medium bg-[#1A9FDA] text-gray-800 rounded'
>
{category.attributes.name}
</span>
))}
</div>
<ReactMarkdown className='mt-2 text-gray-600 text-xl line-break'>{`${articlesmap.attributes.body.substring(0, 150)}...`}</ReactMarkdown>
<Link to={`/article/${articlesmap.id}`} className='text-blue-500 font-medium'>
Read more
</Link>
</div>
</div>
)}
</div>
</div>
</div>
)
}
export default Articles
Dengan penjelasan lebih dalam dari skrip adalah sebagai berikut.
Dalam proses pemanggilan data spesifik, sangat penting bagi kita untuk memahami struktur hirarki respons API dengan baik. Berikut adalah contoh respons API yang kita gunakan:
{
"data": [
{
"id": 1,
"attributes": {
"title": "Menginstal Strapi Headless CMS di Cloud Raya",
"body": "Dalam era pengembangan web yang terus berkembang....",
"createdAt": "2023-07-26T06:28:51.321Z",
"updatedAt": "2023-08-05T15:58:19.057Z",
"publishedAt": "2023-07-26T06:30:20.830Z",
"categories": {
"data": [
{
"id": 1,
"attributes": {
"name": "Integration",
"createdAt": "2023-07-26T10:09:37.750Z",
"updatedAt": "2023-07-26T10:09:45.792Z",
"publishedAt": "2023-07-26T10:09:45.789Z"
}
}
]
},
"coverImage": {
"data": {
"id": 1,
"attributes": {
"name": "cover1.jpg",
"url": "/uploads/cover1.jpg",
{...}
}
}
}
}
},
Karena dalam respons API ini, semua data yang kita butuhkan untuk artikel terletak di dalam hirarki “data”, maka kita menulisnya seperti ini:
// Articles.jsx
/*...*/
{articles.data.map((articlesmap)=>
/*...*/
Dalam potongan skrip di atas:
articles
adalah prop yang mengandung respons data dari API Strapi yang kita panggil.data
adalah array yang berisi data artikel dari respons API.articlesmap
adalah prop yang mewakili setiap elemen dalam array data artikel dari respons API.
Tujuan dari skrip ini adalah untuk melakukan pemetaan terhadap hasil panggilan API yang telah kita lakukan sebelumnya, serta memilih data yang spesifik dalam hirarki “data”.
Selanjutnya, kita hanya perlu menyesuaikan data yang ingin ditampilkan. Sebagai contoh, untuk menampilkan gambar sampul artikel, hirarkinya akan menjadi seperti berikut:
// Articles.jsx
/*...*/
<img src={`http://163.53.195.233:1337${articlesmap.attributes.coverImage.data.attributes.url}`} />
/*...*/
Sama halnya dengan pemanggilan hirarki yang berada di dalam satu tingkat lebih dalam, kita dapat menggunakan tanda titik (.) untuk merujuk pada hirarki tersebut. Sebagai contoh, untuk menampilkan data kategori berdasarkan respons API ini, susunan skripnya akan menjadi seperti berikut:
// Articles.jsx
/*...*/
{articlesmap.attributes.categories.data.map((category) => (
<span>
{category.attributes.name}
</span>
))}
/*...*/
Menampilkan Data Artikel Berdasarkan ID
Oke, mari kita cek kembali skrip pada 'App.jsx'
yang mengatur masalah pengambilan ID dengan menggunakan React Router ini.
// App.jsx
/*...*/
<Route path="/article/:idFromURL" element={<ArticleContentPage articles={articleData} />} />
/*...*/
path="/article/:idFromURL"
menunjukkan bahwa kita sedang menetapkan rute untuk halaman artikel yang menerima suatu nilai ID sebagai parameter.
:idfromURL
adalah salah satu contoh parameter dinamis yang umum digunakan dalam routing untuk membuat rute yang spesifik. Nilai :idfromURL
ini akhirnya akan didapat pada tautan setiap artikel yang sudah kita atur sebelumnya:
// Articles.jsx
/*...*/
<Link to={`/article/${articlesmap.id}`}>...</Link>
/*...*/
Sekarang, mari buka file 'ArticleContentPage.jsx'
terlebih dahulu untuk mengalirkan data dari articles
ke komponen ArticleContent
yang akan menampilkan detail artikel. Sesuaikan skrip menjadi seperti berikut:
// ArticleContentPage.jsx
import React from 'react'
import { Navbar, ArticleContent } from "../components"
const ArticleContentPage = ({articles}) => {
return (
<div>
<Navbar />
<ArticleContent articles={articles}/>
</div>
)
}
export default ArticleContentPage
Kemudian buka 'ArticleContent.jsx'
dan sesuaikan skripnya menjadi seperti berikut:
// ArticleContent.jsx
import React from 'react'
import { useParams } from 'react-router-dom'
import { ReactMarkdown } from 'react-markdown/lib/react-markdown'
const ArticleContent = ({ articles }) => {
// useParams() sebagai hook yang disediakan oleh React Router akan membaca parameter dari URL dan menyimpannya di dalam variabel idFromURL.
const { idFromURL } = useParams()
/*const articles=[...] */
/*
Membuat array baru dengan memfilter data artikel berdasarkan artikel ID yang diambil dari variabel idFromURL.
Jika variabel idFromURL cocok dengan ID artikel yang tersedia, maka gunakan datanya.
Jika tidak cocok, tampilkan pesan error "Article not found".
*/
let articlemap = {};
let array = articles.data.filter((articlemap) => articlemap.id == idFromURL);
if (array.length > 0) {
articlemap = array[0];
} else {
return (
<div className="flex items-center justify-center h-screen">
<p>⛔ Article not found. ⛔</p></div>
);
}
return (
<div className='w-full pb-10 bg-[#f9f9f9]'>
<div className='max-w-7xl mx-auto px-4 py-12 sm:px-6 lg:px-8'>
<div className='grid lg:grid-cols-3 gap-8'>
<div className='col-span-2 bg-white shadow rounded-lg overflow-hidden'>
<img className='h-[300px] w-full object-cover' src={`http://163.53.195.233:1337${articlemap.attributes.coverImage.data.attributes.url}`} />
<div className='p-6'>
<h1 className='text-3xl font-semibold'>{articlemap.attributes.title}</h1>
<div className='flex space-x-2 mt-2'>
{articlemap.attributes.categories.data.map((categorymap) => (
<span key={categorymap.id} className='px-2 py-1 text-sm font-medium bg-[#1A9FDA] text-gray-800 rounded'>
{categorymap.attributes.name}
</span>
))}
</div>
<ReactMarkdown className='mt-2 text-gray-500 text-justify' >{articlemap.attributes.body}</ReactMarkdown>
</div>
</div>
<div className='items-center w-full bg-white rounded-xl drop-shadow-md py-5 max-h-[250px]'>
<div>
<img className='p-2 w-32 h-32 rounded-full mx-auto object-cover' src={`http://163.53.195.233:1337${articlemap.attributes.authorImage.data.attributes.url}`} />
<h1 className='font-bold text-2xl text-center text-gray-900 pt-3'>{articlemap.attributes.authorName}</h1>
<p className='text-center text-gray-900 font-medium'>{articlemap.attributes.authorDesc}</p>
</div>
</div>
</div>
</div>
</div>
)
}
export default ArticleContent
Registrasi Pengguna
Berikut ini adalah implementasi singkat dalam melakukan registrasi pengguna di Strapi menggunakan React.
Untuk fungsi esensial yang kita butuhkan adalah sebagai berikut:
// RegistrationForm.jsx
import React, { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
const RegistrationForm = () => {
// State untuk menyimpan data formulir
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
});
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
// Menggunakan useNavigate dari react-router-dom untuk navigasi halaman
const navigate = useNavigate();
// Mengatur perubahan input dalam formulir
const handleInputChange = (event) => {
const { name, value } = event.target;
setFormData((prevData) => ({
...prevData, // Menyalin nilai sebelumnya dari state formData
[name]: value, // Memperbarui nilai yang sesuai dengan input yang diubah
}));
};
// Menghandle pengiriman formulir
const handleSubmit = async (event) => {
// Mencegah perilaku default browser yang biasanya akan memuat ulang halaman atau berpindah ke halaman lain saat tombol "Register" ditekan
// Kita akan mengambil alih pengiriman data formulir dan mengontrolnya dengan menggunakan fungsi handleSubmit.
event.preventDefault();
// Mengirim data formulir ke server menggunakan metode POST
const response = await fetch('http://163.53.195.233:1337/api/auth/local/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
const responseData = await response.json();
// Jika permintaan berhasil (ditandai dengan terbuatnya JWT token), setIsSucess menjadi true.
if (responseData.jwt) {
setIsSuccess(true);
setIsError(false);
} else {
// Menangani kesalahan jika terjadi masalah selama proses pengiriman
setIsSuccess(false);
setIsError(true);
setErrorMessage(responseData.error.message);
}
};
// navigasikan ke halaman utama ketika klik tombol close.
const handleModalClose = () => {
setIsSuccess(false);
setIsError(false);
if (isSuccess) {
navigate('/');
}
};
return (
<div className="flex justify-center items-center h-screen bg-gray-100">
<div className="w-full max-w-md p-6 bg-white rounded-lg shadow-md">
<h2 className="text-2xl font-semibold mb-4 text-center">Registration</h2>
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label htmlFor="username" className="block text-sm font-medium">
Username
</label>
<input
type="text"
id="username"
name="username"
value={formData.username}
onChange={handleInputChange}
className="mt-1 p-2 w-full border rounded focus:outline-none focus:border-blue-400"
required
/>
</div>
<div className="mb-4">
<label htmlFor="email" className="block text-sm font-medium">
Email
</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleInputChange}
className="mt-1 p-2 w-full border rounded focus:outline-none focus:border-blue-400"
required
/>
</div>
<div className="mb-4">
<label htmlFor="password" className="block text-sm font-medium">
Password
</label>
<input
type="password"
id="password"
name="password"
value={formData.password}
onChange={handleInputChange}
className="mt-1 p-2 w-full border rounded focus:outline-none focus:border-blue-400"
required
/>
</div>
<button
type="submit"
className="w-full py-2 px-4 bg-[#202E60] rounded-md hover:bg-[#30448c] hover:text-white"
>
Register
</button>
</form>
<div className='py-5 border-t border-gray-300 mt-5'>
<p class="text-sm text-center text-gray-400">Already have an account? <Link to={`/login`} class="text-indigo-400 focus:outline-none focus:underline focus:text-indigo-500 dark:focus:border-indigo-800">Login</Link>.</p>
</div>
</div>
{isSuccess && (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
<div className="bg-white p-6 rounded shadow-md text-center">
<p className="text-green-500 font-semibold mb-2">Registration successful</p>
<button
onClick={handleModalClose}
className="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600 focus:outline-none"
>
Close
</button>
</div>
</div>
)}
{isError && (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
<div className="bg-white p-6 rounded shadow-md text-center">
<p className="text-red-500 font-semibold mb-2">{errorMessage}</p>
<button
onClick={handleModalClose}
className="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 focus:outline-none"
>
Close
</button>
</div>
</div>
)}
</div>
);
};
export default RegistrationForm;
Login Pengguna
Proses login mengikuti alur yang hampir mirip dengan proses registrasi, namun perlu dilakukan penyesuaian pada URL endpoint-nya:
// LoginForm.jsx
import React, { useState } from 'react';
import { useNavigate, Link, Route } from 'react-router-dom';
const LoginForm = () => {
const [formData, setFormData] = useState({
identifier: '',
password: '',
});
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const navigate = useNavigate();
const handleInputChange = (event) => {
const { name, value } = event.target;
setFormData((prevData) => ({
...prevData,
[name]: value,
}));
};
const handleSubmit = async (event) => {
event.preventDefault();
const response = await fetch('http://163.53.195.233:1337/api/auth/local', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
const responseData = await response.json();
if (responseData.jwt) {
setIsSuccess(true);
setIsError(false);
} else {
setIsSuccess(false);
setIsError(true);
setErrorMessage(responseData.error.message);
}
};
const handleModalClose = () => {
setIsSuccess(false);
setIsError(false);
if (isSuccess) {
navigate('/');
}
};
return (
<div className="flex justify-center items-center h-screen bg-gray-100">
<div className="w-full max-w-md p-6 bg-white rounded-lg shadow-md">
<h2 className="text-2xl font-semibold mb-4 text-center">Login</h2>
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label htmlFor="identifier" className="block text-sm font-medium">
Username
</label>
<input
type="text"
id="identifier"
name="identifier"
value={formData.username}
onChange={handleInputChange}
className="mt-1 p-2 w-full border rounded focus:outline-none focus:border-blue-400"
required
/>
</div>
<div className="mb-4">
<label htmlFor="password" className="block text-sm font-medium">
Password
</label>
<input
type="password"
id="password"
name="password"
value={formData.password}
onChange={handleInputChange}
className="mt-1 p-2 w-full border rounded focus:outline-none focus:border-blue-400"
required
/>
</div>
<button type="submit" className="w-full py-2 px-4 bg-[#202E60] rounded-md hover:bg-[#30448c] hover:text-white">
Login
</button>
</form>
<div className='py-5 border-t border-gray-300 mt-5'>
<p className="text-sm text-center text-gray-400">Don't have an account yet? <Link to={`/registration`} className="text-indigo-400 focus:outline-none focus:underline focus:text-indigo-500 dark:focus:border-indigo-800">Sign up</Link>.</p>
</div>
</div>
{isSuccess && (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
<div className="bg-white p-6 rounded shadow-md text-center">
<p className="text-green-500 font-semibold mb-2">Login success</p>
<button
onClick={handleModalClose}
className="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600 focus:outline-none"
>
Close
</button>
</div>
</div>
)}
{isError && (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
<div className="bg-white p-6 rounded shadow-md text-center">
<p className="text-red-500 font-semibold mb-2">{errorMessage}</p>
<button
onClick={handleModalClose}
className="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 focus:outline-none"
>
Close
</button>
</div>
</div>
)}
</div>
)
};
export default LoginForm;
Setiap kali login berhasil, jika kita aktifkan console.log(responseData.jwt)
pada skrip, akan ditampilkan JWT (Javascript Web Token) sesuai pembahasan pada artikel sebelumnya.
JWT ini yang akan kita simpan dalam cookie pada browser untuk menjaga kelangsungan sesi.
Aktifitas lanjutan
▶ Manajemen Sesi Login dengan Cookie
Untuk memastikan penyimpanan sesi login berjalan dengan baik, salah satu metodenya adalah dengan menyimpan token JWT di dalam cookie.
Dalam manajemen cookie, ada berbagai cara, seperti menggunakan document.cookie
atau mengelola cookie secara manual di server. Namun, untuk kepraktisan dan kejelasan dalam penulisan skrip, kita dapat memanfaatkan js-cookie
, sebuah library yang membantu dalam pengaturan cookie.
Pertama-tama, kita perlu menginstal library js-cookie
dengan perintah:
npm install js-cookie
Setelah instalasi selesai, kita dapat mengimpor library ini di dalam file 'LoginForm.jsx'
sebelumnya:
// LoginForm.jsx
/*...*/
import Cookies from 'js-cookie';
/*...*/
Langkah selanjutnya adalah menambahkan konfigurasi untuk menyimpan cookie saat login berhasil.
// LoginForm.jsx
/*...*/
// Jika permintaan berhasil (ditandai dengan terbuatnya JWT token), navigasikan ke halaman utama
if (responseData.jwt) {
// Set JWT token dengan js-cookie
Cookies.set('jwt', responseData.jwt, {
expires: 30 / (60 * 24), // menghitung waktu kedaluwarsa cookie 30 menit dalam bentuk fraksi dari 1 hari (24 jam), di mana 1 hari terdiri dari 60 * 24 menit.
});
navigate('/');
}
/*...*/
Setelah itu, kita dapat mencoba untuk melakukan login dan memeriksa cookie pada browser. Anda akan melihat bahwa token JWT telah tersimpan dengan nama 'jwt'
.
Walaupun langkah-langkah ini relatif sederhana, penting untuk mengingat bahwa pengaturan cookie bisa lebih kompleks tergantung pada kebutuhan aplikasi Anda. Beberapa opsi cookie seperti domain, path, httponly, dan secure mungkin perlu disesuaikan. Pastikan untuk merujuk pada dokumentasi js-cookie
atau panduan umum tentang cookie untuk memastikan pengaturan yang tepat.
▶ Tampilkan Tombol Logout setelah Berhasil Login
Kita bisa menggunakan Cookies.get('jwt')
untuk memeriksa apakah token ada dalam cookie. Jika ada, tombol login dan register akan digantikan dengan tombol logout.
//Navbar.jsx
/*...*/
import Cookies from 'js-cookie'; // Import js-cookie library
const Navbar = () => {
/*...*/
const handleLogout = () => {
Cookies.remove('jwt'); // Menghapus token pada logout
};
return (
<div>
<div>
{/*...*/}
{Cookies.get('jwt') ? ( // Cek apakah token ada dalam cookie
<div>
<button onClick={handleLogout}>Logout</button>
</div>
) : (
<div>
<Link to={`/login`}><button>Login</button></Link>
<Link to={`/registration`}><button>Register</button></Link>
</div>
)}
{/*...*/}
</div>
</div>
)
}
export default Navbar;
Kesimpulan
Dalam tutorial ini, kita telah memahami cara mengelola data konten dari backend Strapi dan kemudian menampilkannya pada frontend aplikasi.
Adapun tutorial series lainnya yang dapat melengkapi pemahaman Anda tentang pengembangan dengan Strapi dapat ditemukan sebelum atau sesudah artikel ini, atau Anda dapat mengeksplorasi berbagai tutorial menarik lainnya di halaman Knowledge Base (KB) Cloud Raya.