Bab 8: Menjaga Komponen Tetap Pure
⏱ 6 menit bacaApa Itu "Pure"?
Sebelum masuk ke React, kita pahami dulu konsep "pure" (murni) di dunia nyata.
Bayangin mesin pembuat jus di pasar. Kamu masukin 3 buah jeruk, keluar jus jeruk. Besok kamu masukin 3 buah jeruk lagi, keluar jus jeruk yang persis sama. Mesin ini nggak tiba-tiba ngasih jus mangga padahal kamu masukin jeruk. Mesin ini juga nggak diam-diam nyimpen sisa jus dari pelanggan sebelumnya dan nyampurin ke jus kamu.
Mesin jus ini pure: input yang sama selalu menghasilkan output yang sama, dan nggak ada efek samping ke dunia luar.
Sekarang bayangin mesin jus yang "nggak pure": kadang ngasih jus jeruk, kadang jus campur, tergantung mood mesinnya atau tergantung siapa pelanggan sebelumnya. Kamu nggak bisa prediksi hasilnya. Serem kan?
Pure Function di JavaScript
Sebelum ke React, pahami dulu pure function di JavaScript biasa:
// ✅ PURE FUNCTION - input sama = output sama, tanpa efek samping
function tambah(a, b) {
return a + b;
}
tambah(2, 3); // Selalu 5
tambah(2, 3); // Tetap 5, kapanpun dipanggil
function formatNama(depan, belakang) {
return `${depan} ${belakang}`;
}
formatNama("Budi", "Santoso"); // Selalu "Budi Santoso"
// ❌ IMPURE FUNCTION - hasilnya bisa beda tiap dipanggil
let counter = 0;
function tambahCounter() {
counter++; // Mengubah variabel di luar fungsi (side effect!)
return counter;
}
tambahCounter(); // 1
tambahCounter(); // 2 (beda! padahal nggak ada input)
tambahCounter(); // 3 (beda lagi!)
function waktuSekarang() {
return new Date().toLocaleTimeString(); // Hasilnya beda tiap detik
}Ciri-ciri Pure Function:
- Deterministic: Input yang sama SELALU menghasilkan output yang sama
- No side effects: Nggak mengubah apapun di luar fungsinya (nggak ubah variabel global, nggak nulis ke database, nggak ubah DOM langsung)
- Nggak bergantung pada state eksternal: Nggak baca variabel global yang bisa berubah
Komponen React Sebagai Pure Function
React menganggap setiap komponen sebagai pure function. Artinya:
Kalau props dan state yang sama diberikan ke komponen, komponen itu harus SELALU menghasilkan JSX yang sama.
// ✅ PURE COMPONENT - props sama = output sama
function Sapaan({ nama, jam }) {
const waktu = jam < 12 ? "Pagi" : jam < 17 ? "Siang" : "Malam";
return <h1>Selamat {waktu}, {nama}!</h1>;
}
// Kalau dipanggil dengan props yang sama, hasilnya SELALU sama:
<Sapaan nama="Budi" jam={9} /> // Selalu: "Selamat Pagi, Budi!"
<Sapaan nama="Budi" jam={9} /> // Tetap: "Selamat Pagi, Budi!"// ❌ IMPURE COMPONENT - hasilnya bisa beda tiap render!
let panggilanKe = 0;
function SapaanBuruk({ nama }) {
panggilanKe++; // Mengubah variabel di luar komponen!
return <h1>Halo {nama}! (render ke-{panggilanKe})</h1>;
}
// Render pertama: "Halo Budi! (render ke-1)"
// Render kedua: "Halo Budi! (render ke-2)" ← BEDA! Padahal props sama!Kenapa React Butuh Komponen Pure?
Ini bukan cuma soal "best practice" atau "biar rapi." Ada alasan teknis yang kuat:
1. React Bisa Render Ulang Kapan Saja
React berhak me-render ulang komponen kamu kapanpun dia mau. Kalau komponen pure, nggak masalah. Hasilnya tetap sama. Tapi kalau impure, setiap re-render bisa menghasilkan hal berbeda dan bikin bug.
// ✅ Pure - aman di-render berapa kali pun
function Harga({ jumlah, hargaSatuan }) {
return <p>Total: Rp {(jumlah * hargaSatuan).toLocaleString('id-ID')}</p>;
}
// Render 1x atau 100x, hasilnya tetap sama kalau props sama
// ❌ Impure - bahaya kalau di-render ulang
let totalGlobal = 0;
function HargaBahaya({ harga }) {
totalGlobal += harga; // Setiap render MENAMBAH total!
return <p>Total: Rp {totalGlobal.toLocaleString('id-ID')}</p>;
}
// Render 1x: Total 15000
// Render 2x: Total 30000 (BUG!)
// Render 3x: Total 45000 (MAKIN PARAH!)2. React Bisa Skip Render (Optimization)
Kalau React tahu komponen kamu pure dan props-nya nggak berubah, dia bisa skip rendering komponen itu (memoization). Ini bikin aplikasi lebih cepat. Tapi ini cuma aman kalau komponen benar-benar pure.
3. React Bisa Render di Server
Dengan Server Components dan SSR, komponen bisa di-render di server. Kalau komponen pure, hasilnya konsisten di mana pun di-render (server atau client). Kalau impure, bisa beda hasilnya.
4. StrictMode Double Render
Di development mode, React sengaja me-render komponen dua kali untuk mendeteksi impurity. Kalau komponen kamu pure, double render nggak masalah (hasilnya sama). Kalau impure, kamu bakal lihat bug.
// Di StrictMode, React panggil komponen 2x
// Pure component: render 1 = render 2 ✅
// Impure component: render 1 ≠ render 2 ❌ (bug terdeteksi!)Side Effects: Musuh Purity
Side effect adalah apapun yang "menyentuh dunia luar" dari dalam komponen saat rendering. Contoh side effects:
// ❌ Side effects SAAT RENDERING (jangan dilakukan!)
// 1. Mengubah variabel di luar komponen
let hitungan = 0;
function Counter() {
hitungan++; // Side effect!
return <p>{hitungan}</p>;
}
// 2. Mengubah DOM langsung
function Judul({ teks }) {
document.title = teks; // Side effect!
return <h1>{teks}</h1>;
}
// 3. Mengubah objek/array yang sudah ada
function DaftarItem({ items }) {
items.push("item baru"); // Side effect! Mutasi props!
return <ul>{items.map(i => <li key={i}>{i}</li>)}</ul>;
}
// 4. Melakukan HTTP request
function DataUser({ userId }) {
fetch(`/api/users/${userId}`); // Side effect!
return <p>Loading...</p>;
}
// 5. Menulis ke localStorage
function Tema({ mode }) {
localStorage.setItem('tema', mode); // Side effect!
return <div className={mode}>...</div>;
}Local Mutation: Ini Boleh!
Ada satu pengecualian penting: mutasi lokal (mengubah variabel/objek yang dibuat DI DALAM komponen saat render yang sama) itu boleh dan aman.
Analoginya: Kalau kamu masak di dapur sendiri, kamu boleh ngacak-ngacak dapur kamu. Yang nggak boleh itu ngacak-ngacak dapur tetangga. Selama "kekacauan" itu terjadi di dalam dan nggak bocor keluar, nggak masalah.
// ✅ Local mutation - AMAN!
function DaftarAngka() {
// Variabel ini dibuat BARU setiap render
const items = []; // Array baru, milik render ini
for (let i = 1; i <= 10; i++) {
items.push(<li key={i}>Item {i}</li>); // Mutasi array LOKAL - aman!
}
return <ul>{items}</ul>;
}// ✅ Local mutation - AMAN!
function TabelPerkalian({ angka }) {
// Objek baru dibuat setiap render
const baris = [];
for (let i = 1; i <= 10; i++) {
baris.push(
<tr key={i}>
<td>{angka} × {i}</td>
<td>=</td>
<td>{angka * i}</td>
</tr>
);
}
return (
<table>
<tbody>{baris}</tbody>
</table>
);
}// ✅ Membuat objek baru berdasarkan props - AMAN!
function ProfilLengkap({ user }) {
// Bikin objek baru, nggak mengubah props
const profilFormatted = {
namaLengkap: `${user.depan} ${user.belakang}`,
inisial: `${user.depan[0]}${user.belakang[0]}`,
umur: new Date().getFullYear() - user.tahunLahir,
};
return (
<div>
<h2>{profilFormatted.namaLengkap}</h2>
<span className="inisial">{profilFormatted.inisial}</span>
<p>Umur: {profilFormatted.umur} tahun</p>
</div>
);
}Bedakan: Local Mutation vs External Mutation
// ✅ LOCAL mutation - variabel dibuat di dalam, dimutasi di dalam
function Komponen() {
const arr = []; // Dibuat di sini
arr.push("item"); // Dimutasi di sini - AMAN
return <p>{arr.length}</p>;
}
// ❌ EXTERNAL mutation - variabel dari luar dimutasi di dalam
const globalArr = []; // Dibuat di LUAR
function Komponen() {
globalArr.push("item"); // Mutasi variabel luar - BAHAYA!
return <p>{globalArr.length}</p>;
}
// ❌ PROPS mutation - props dimutasi
function Komponen({ items }) {
items.sort(); // Mutasi props - BAHAYA!
return <ul>{items.map(i => <li key={i}>{i}</li>)}</ul>;
}
// ✅ COPY then mutate - bikin copy, mutasi copy-nya
function Komponen({ items }) {
const sorted = [...items].sort(); // Copy baru, mutasi copy - AMAN
return <ul>{sorted.map(i => <li key={i}>{i}</li>)}</ul>;
}Di Mana Side Effects Boleh Dilakukan?
Side effects itu bukan hal jahat. Aplikasi BUTUH side effects (fetch data, update DOM, simpan ke storage). Yang penting: jangan lakukan saat rendering. Lakukan di tempat yang tepat:
1. Event Handlers (Paling Umum)
function FormKontak() {
function handleSubmit(e) {
e.preventDefault();
// ✅ Side effect di event handler - AMAN!
fetch('/api/kontak', {
method: 'POST',
body: JSON.stringify({ nama: 'Budi', pesan: 'Halo' })
});
alert('Pesan terkirim!'); // Side effect - aman di sini
}
function handleKlik() {
// ✅ Side effect di event handler - AMAN!
console.log('Tombol diklik');
document.title = 'Diklik!';
localStorage.setItem('lastClick', Date.now());
}
return (
<form onSubmit={handleSubmit}>
<button onClick={handleKlik}>Kirim</button>
</form>
);
}Event handler nggak dijalankan saat rendering. Mereka dijalankan sebagai respons terhadap aksi user (klik, submit, dll). Jadi side effects di sini aman.
2. useEffect (Untuk Side Effects yang Perlu Sinkronisasi)
import { useEffect } from 'react';
function HalamanProduk({ produkId }) {
const [produk, setProduk] = useState(null);
// ✅ Side effect di useEffect - dijalankan SETELAH render
useEffect(() => {
// Fetch data setelah komponen di-render
fetch(`/api/produk/${produkId}`)
.then(res => res.json())
.then(data => setProduk(data));
}, [produkId]);
// ✅ Side effect di useEffect - update document title
useEffect(() => {
document.title = produk ? produk.nama : 'Loading...';
}, [produk]);
// Render tetap pure - cuma baca state dan props
if (!produk) return <p>Memuat...</p>;
return <h1>{produk.nama}</h1>;
}useEffect menjalankan kode setelah render selesai. Jadi rendering tetap pure, dan side effects terjadi terpisah.
Catatan: useEffect dibahas detail di Part 3. Untuk sekarang, cukup tahu bahwa ini tempat yang tepat untuk side effects yang perlu terjadi setelah render.
3. Kapan Pakai Event Handler vs useEffect?
| Situasi | Pakai |
|---|---|
| User klik tombol → fetch data | Event handler |
| User submit form → kirim ke server | Event handler |
| Komponen muncul → fetch data awal | useEffect |
| Props berubah → update document title | useEffect |
| Props berubah → sinkronisasi dengan external system | useEffect |
Aturan praktis: Kalau side effect dipicu oleh aksi user, pakai event handler. Kalau dipicu oleh rendering (komponen muncul atau props/state berubah), pakai useEffect.
StrictMode: Detektif Impurity
React punya fitur bernama StrictMode yang membantu mendeteksi komponen impure. Di development mode, StrictMode me-render setiap komponen dua kali.
// Di index.js atau main.jsx:
import { StrictMode } from 'react';
root.render(
<StrictMode>
<App />
</StrictMode>
);Kenapa Double Render Membantu?
// Komponen impure - ketahuan di StrictMode!
let idCounter = 0;
function ItemBaru() {
idCounter++; // Side effect!
return <p>Item #{idCounter}</p>;
}
// Tanpa StrictMode:
// Render 1: "Item #1" ← keliatan bener
// Dengan StrictMode (double render):
// Render 1: "Item #1"
// Render 2: "Item #2" ← BEDA! Bug terdeteksi!// Komponen pure - aman di StrictMode
function ItemBaru({ nomor }) {
return <p>Item #{nomor}</p>;
}
// Dengan StrictMode (double render):
// Render 1: "Item #5"
// Render 2: "Item #5" ← SAMA! Aman ✅Penting: StrictMode cuma aktif di development. Di production, komponen di-render sekali seperti biasa. Jadi nggak ada performance impact di production.
Contoh Nyata: Refactoring Impure ke Pure
Sebelum (Impure):
// ❌ IMPURE - banyak masalah
let guestCount = 0;
function NomorTamu() {
guestCount++; // Mutasi variabel luar
// Langsung manipulasi DOM
document.getElementById('counter').innerText = guestCount;
return (
<div>
<h2>Tamu #{guestCount}</h2>
<p>Waktu: {new Date().toLocaleTimeString()}</p>
</div>
);
}Sesudah (Pure):
// ✅ PURE - bersih dan predictable
function NomorTamu({ nomor }) {
// Semua data dari props - nggak ada variabel luar
return (
<div>
<h2>Tamu #{nomor}</h2>
</div>
);
}
// Side effects dipindah ke tempat yang tepat
function DaftarTamu() {
const [jumlahTamu, setJumlahTamu] = useState(0);
// Side effect di useEffect
useEffect(() => {
document.title = `${jumlahTamu} tamu terdaftar`;
}, [jumlahTamu]);
// Event handler untuk aksi user
function handleTambahTamu() {
setJumlahTamu(jumlahTamu + 1);
}
return (
<div>
<button onClick={handleTambahTamu}>Tambah Tamu</button>
{Array.from({ length: jumlahTamu }, (_, i) => (
<NomorTamu key={i + 1} nomor={i + 1} />
))}
</div>
);
}Contoh Lengkap: Komponen Pure vs Impure
// ===== VERSI IMPURE (JANGAN DITIRU!) =====
let pesananGlobal = []; // Variabel global - bahaya!
function FormPesananBuruk({ menu }) {
// ❌ Mutasi variabel global saat render
pesananGlobal.push(menu);
// ❌ Langsung ubah DOM
document.title = `${pesananGlobal.length} pesanan`;
// ❌ Baca waktu saat render (nggak deterministic)
const waktuPesan = new Date().toLocaleTimeString();
return (
<div>
<p>Pesanan: {menu}</p>
<p>Waktu: {waktuPesan}</p>
<p>Total pesanan: {pesananGlobal.length}</p>
</div>
);
}
// ===== VERSI PURE (CONTOH YANG BENAR) =====
function ItemPesanan({ menu, nomor, waktu }) {
// ✅ Semua data dari props
// ✅ Nggak ada side effect
// ✅ Output selalu sama untuk input yang sama
return (
<div className="item-pesanan">
<span className="nomor">#{nomor}</span>
<span className="menu">{menu}</span>
<span className="waktu">{waktu}</span>
</div>
);
}
function DaftarPesanan() {
const [pesanan, setPesanan] = useState([]);
// ✅ Side effect di useEffect
useEffect(() => {
document.title = `${pesanan.length} pesanan`;
}, [pesanan]);
// ✅ Side effect di event handler
function handleTambahPesanan(menu) {
const pesananBaru = {
id: Date.now(),
menu: menu,
waktu: new Date().toLocaleTimeString(),
};
setPesanan([...pesanan, pesananBaru]); // State update, bukan mutasi
}
return (
<div>
<h2>Daftar Pesanan ({pesanan.length})</h2>
<div className="tombol-menu">
<button onClick={() => handleTambahPesanan("Nasi Goreng")}>
+ Nasi Goreng
</button>
<button onClick={() => handleTambahPesanan("Mie Ayam")}>
+ Mie Ayam
</button>
<button onClick={() => handleTambahPesanan("Es Teh")}>
+ Es Teh
</button>
</div>
{/* Komponen pure - cuma terima props dan render */}
{pesanan.map((p, index) => (
<ItemPesanan
key={p.id}
menu={p.menu}
nomor={index + 1}
waktu={p.waktu}
/>
))}
</div>
);
}Checklist: Apakah Komponen Saya Pure?
Tanya diri sendiri:
- ✅ Apakah komponen ini menghasilkan output yang sama kalau props/state sama?
- ✅ Apakah komponen ini TIDAK mengubah variabel di luar dirinya?
- ✅ Apakah komponen ini TIDAK mengubah props yang diterima?
- ✅ Apakah komponen ini TIDAK melakukan fetch/DOM manipulation/localStorage saat render?
- ✅ Apakah semua "kekacauan" (mutasi) terjadi pada variabel yang dibuat di dalam komponen?
Kalau semua jawaban "ya", komponen kamu pure! 🎉
⚠️ Jebakan
Jebakan 1: Mutasi Props
// ❌ Mengubah props - IMPURE!
function Daftar({ items }) {
items.push("item baru"); // Mutasi array dari parent!
items.sort(); // Mutasi lagi!
return <ul>{items.map(i => <li key={i}>{i}</li>)}</ul>;
}
// ✅ Bikin copy baru
function Daftar({ items }) {
const sorted = [...items, "item baru"].sort();
return <ul>{sorted.map(i => <li key={i}>{i}</li>)}</ul>;
}Jebakan 2: Variabel di Luar Komponen
// ❌ Variabel di luar yang dimutasi saat render
let renderCount = 0;
function App() {
renderCount++; // Berubah setiap render!
return <p>Render ke-{renderCount}</p>;
}
// ✅ Pakai state kalau butuh counter
function App() {
// useRef untuk counter yang nggak trigger re-render
const renderCount = useRef(0);
renderCount.current++;
// Atau useEffect untuk logging
useEffect(() => {
console.log('Component rendered');
});
return <p>App</p>;
}Jebakan 3: Membaca Waktu/Random Saat Render
// ❌ Nggak deterministic - hasilnya beda tiap render
function Komponen() {
const sekarang = new Date(); // Beda tiap render!
const random = Math.random(); // Beda tiap render!
return <p>{sekarang.toString()} - {random}</p>;
}
// ✅ Terima sebagai props atau hitung di event handler
function Komponen({ waktuDibuat }) {
return <p>Dibuat: {waktuDibuat}</p>;
}
// Atau pakai state yang di-set sekali
function Komponen() {
const [waktu] = useState(() => new Date().toLocaleTimeString());
return <p>Dibuat: {waktu}</p>;
}Jebakan 4: Mengubah DOM Langsung
// ❌ Manipulasi DOM saat render
function Judul({ teks }) {
document.title = teks; // Side effect saat render!
document.body.style.backgroundColor = 'blue'; // Side effect!
return <h1>{teks}</h1>;
}
// ✅ Pakai useEffect
function Judul({ teks }) {
useEffect(() => {
document.title = teks;
}, [teks]);
return <h1>{teks}</h1>;
}Jebakan 5: Bingung "Kapan Boleh Mutasi"
// Aturan simpel:
// - Dibuat DI DALAM komponen, SAAT render yang sama → BOLEH dimutasi
// - Dibuat DI LUAR atau diterima sebagai PROPS → JANGAN dimutasi
function Komponen({ dataDariLuar }) {
// ✅ Dibuat di dalam - boleh dimutasi
const arrLokal = [];
arrLokal.push("item"); // Aman!
const objLokal = {};
objLokal.nama = "Budi"; // Aman!
// ❌ Dari luar - jangan dimutasi
dataDariLuar.push("item"); // Bahaya!
dataDariLuar.nama = "Budi"; // Bahaya!
// ✅ Kalau perlu "ubah" data dari luar, bikin copy
const copyData = [...dataDariLuar, "item"]; // Aman!
const copyObj = { ...dataDariLuar, nama: "Budi" }; // Aman!
return <div>...</div>;
}Ringkasan
| Konsep | Penjelasan |
|---|---|
| Pure component | Input sama = output sama, tanpa side effect |
| Side effect | Apapun yang "menyentuh dunia luar" saat render |
| Local mutation | Mutasi variabel yang dibuat di dalam komponen (AMAN) |
| Event handler | Tempat yang tepat untuk side effects dari aksi user |
| useEffect | Tempat untuk side effects yang perlu sinkronisasi |
| StrictMode | Double render untuk deteksi impurity |
| Jangan mutasi props | Selalu bikin copy kalau perlu "ubah" data dari luar |
🏋️ Challenge
Challenge 1: Identifikasi Impurity
Perhatikan komponen berikut. Identifikasi semua masalah purity-nya dan perbaiki:
let totalPesanan = 0;
const riwayat = [];
function KartuPesanan({ nama, harga }) {
totalPesanan += harga;
riwayat.push({ nama, harga, waktu: new Date() });
document.title = `Total: Rp ${totalPesanan}`;
return (
<div>
<h3>{nama}</h3>
<p>Harga: Rp {harga.toLocaleString('id-ID')}</p>
<p>Total semua: Rp {totalPesanan.toLocaleString('id-ID')}</p>
<p>Pesanan ke-{riwayat.length}</p>
</div>
);
}💡 Hint
Masalah:
- Mutasi
totalPesanan(variabel luar) - Mutasi
riwayat(variabel luar) document.titlediubah saat rendernew Date()bikin output nggak deterministic
Solusi: pindahkan state ke dalam komponen parent, pakai useEffect untuk document.title
✅ Solusi
import { useState, useEffect } from 'react';
// ✅ Komponen pure - cuma terima props dan render
function KartuPesanan({ nama, harga, nomor }) {
return (
<div>
<h3>{nama}</h3>
<p>Harga: Rp {harga.toLocaleString('id-ID')}</p>
<p>Pesanan ke-{nomor}</p>
</div>
);
}
// ✅ State dan side effects dikelola di parent
function DaftarPesanan() {
const [pesanan, setPesanan] = useState([]);
// Total dihitung dari state (derived state)
const totalPesanan = pesanan.reduce((sum, p) => sum + p.harga, 0);
// Side effect di useEffect
useEffect(() => {
document.title = `Total: Rp ${totalPesanan.toLocaleString('id-ID')}`;
}, [totalPesanan]);
// Side effect di event handler
function handleTambahPesanan(nama, harga) {
setPesanan([...pesanan, {
id: Date.now(),
nama,
harga,
waktu: new Date()
}]);
}
return (
<div>
<h2>Total: Rp {totalPesanan.toLocaleString('id-ID')}</h2>
<button onClick={() => handleTambahPesanan("Nasi Goreng", 15000)}>
+ Nasi Goreng
</button>
<button onClick={() => handleTambahPesanan("Es Teh", 5000)}>
+ Es Teh
</button>
{pesanan.map((p, index) => (
<KartuPesanan
key={p.id}
nama={p.nama}
harga={p.harga}
nomor={index + 1}
/>
))}
</div>
);
}Challenge 2: Pure Formatting Component
Buat komponen FormatUang yang:
- Menerima props:
jumlah(number),mata_uang("IDR", "USD", "EUR"),tampilSimbol(boolean) - Me-return string yang terformat dengan benar
- HARUS pure: input sama = output sama, tanpa side effect
- Handle edge cases: jumlah negatif, jumlah 0, mata uang nggak dikenal
💡 Hint
- Bikin object mapping untuk simbol dan format
- Semua logika bisa dilakukan tanpa side effect
- Pakai
Math.abs()untuk handle negatif - Default value untuk mata uang nggak dikenal
✅ Solusi
// ✅ 100% Pure - nggak ada side effect, deterministic
function FormatUang({ jumlah, mata_uang = "IDR", tampilSimbol = true }) {
// Config per mata uang (objek lokal, dibuat tiap render - aman)
const config = {
IDR: { simbol: 'Rp', locale: 'id-ID', desimal: 0 },
USD: { simbol: '$', locale: 'en-US', desimal: 2 },
EUR: { simbol: '€', locale: 'de-DE', desimal: 2 },
};
// Fallback untuk mata uang nggak dikenal
const cfg = config[mata_uang] || { simbol: mata_uang, locale: 'en-US', desimal: 2 };
// Handle negatif
const isNegatif = jumlah < 0;
const nilaiAbsolut = Math.abs(jumlah);
// Format angka
const formatted = nilaiAbsolut.toLocaleString(cfg.locale, {
minimumFractionDigits: cfg.desimal,
maximumFractionDigits: cfg.desimal,
});
// Susun output
const simbol = tampilSimbol ? cfg.simbol + ' ' : '';
const tanda = isNegatif ? '-' : '';
// Style berdasarkan nilai
const warna = isNegatif ? 'red' : jumlah === 0 ? '#999' : 'inherit';
return (
<span style={{ color: warna, fontVariantNumeric: 'tabular-nums' }}>
{tanda}{simbol}{formatted}
</span>
);
}
// Penggunaan - hasilnya SELALU sama untuk input yang sama:
function ContohPenggunaan() {
return (
<div>
<p>Saldo: <FormatUang jumlah={1500000} mata_uang="IDR" /></p>
<p>Harga: <FormatUang jumlah={29.99} mata_uang="USD" /></p>
<p>Rugi: <FormatUang jumlah={-50000} mata_uang="IDR" /></p>
<p>Kosong: <FormatUang jumlah={0} mata_uang="EUR" /></p>
<p>Unknown: <FormatUang jumlah={100} mata_uang="BTC" /></p>
<p>Tanpa simbol: <FormatUang jumlah={75000} tampilSimbol={false} /></p>
</div>
);
}Challenge 3: Refactor Impure Component
Refactor komponen berikut agar pure. Komponen ini menampilkan daftar tugas dan punya beberapa masalah purity:
let nextId = 1;
const allTasks = [];
function TaskManager({ initialTasks }) {
// Mutasi array luar
initialTasks.forEach(task => {
task.id = nextId++;
allTasks.push(task);
});
// Sort mutasi array
allTasks.sort((a, b) => a.priority - b.priority);
// Side effect
console.log('Rendered with', allTasks.length, 'tasks');
localStorage.setItem('taskCount', allTasks.length);
return (
<div>
<h2>Tasks ({allTasks.length})</h2>
{allTasks.map(task => (
<div key={task.id}>
<span>{task.nama}</span>
<span>Priority: {task.priority}</span>
</div>
))}
</div>
);
}💡 Hint
Masalah:
nextIddanallTasksadalah variabel luar yang dimutasiinitialTasks.forEachmemutasi props (menambah.id)allTasks.sort()mutasi arrayconsole.logdanlocalStorageadalah side effects saat render
Solusi: semua data jadi state/props, side effects ke useEffect/event handler
✅ Solusi
import { useState, useEffect } from 'react';
// ✅ Komponen item - pure
function TaskItem({ nama, priority }) {
return (
<div style={{
padding: '8px',
borderLeft: `3px solid ${priority <= 2 ? 'red' : priority <= 4 ? 'orange' : 'green'}`,
marginBottom: '4px'
}}>
<span>{nama}</span>
<span style={{ marginLeft: '12px', color: '#666' }}>
Priority: {priority}
</span>
</div>
);
}
// ✅ Komponen manager - state dikelola dengan benar
function TaskManager({ initialTasks }) {
// State internal - bukan variabel global
const [tasks] = useState(() => {
// Inisialisasi state: bikin copy dengan ID, TANPA mutasi props
return initialTasks.map((task, index) => ({
...task, // Copy properti, nggak mutasi original
id: index + 1, // Tambah ID di copy baru
}));
});
// Derived data: sort tanpa mutasi (bikin copy baru)
const sortedTasks = [...tasks].sort((a, b) => a.priority - b.priority);
// Side effects di useEffect
useEffect(() => {
console.log('Rendered with', tasks.length, 'tasks');
localStorage.setItem('taskCount', tasks.length);
}, [tasks.length]);
return (
<div>
<h2>Tasks ({sortedTasks.length})</h2>
{sortedTasks.map(task => (
<TaskItem
key={task.id}
nama={task.nama}
priority={task.priority}
/>
))}
</div>
);
}
// Penggunaan:
function App() {
const tugasAwal = [
{ nama: "Deploy ke production", priority: 1 },
{ nama: "Fix bug login", priority: 2 },
{ nama: "Update dokumentasi", priority: 5 },
{ nama: "Review PR", priority: 3 },
];
return <TaskManager initialTasks={tugasAwal} />;
}Sudah paham materi ini?
Tandai sebagai selesai untuk melacak progress-mu.