Apa itu Component Lifecycle di React? Penjelasan Lengkap untuk Pemula
Pendahuluan
Pernahkah kamu bertanya-tanya apa yang terjadi di balik layar saat komponen React muncul di halaman, berubah, lalu menghilang? Semua itu diatur oleh sesuatu yang disebut Component Lifecycle — siklus hidup sebuah komponen.
Bayangkan lifecycle seperti siklus hidup manusia: lahir, tumbuh dewasa, lalu meninggal. Komponen React pun mengalami hal serupa: mount (muncul di DOM), update (berubah saat state atau props berubah), dan unmount (dihapus dari DOM).
Memahami lifecycle sangat penting agar kamu bisa mengontrol kapan data di-fetch, kapan event listener dipasang atau dilepas, dan bagaimana mencegah memory leak di aplikasimu. Jika kamu sudah familiar dengan State dan Hooks di React sebelumnya, artikel ini akan melengkapi pemahamanmu secara menyeluruh.
Memahami Konsep Dasar Component Lifecycle
Setiap komponen React melewati tiga fase utama:
| Fase | Penjelasan |
|---|---|
| Mounting | Komponen dibuat dan dimasukkan ke dalam DOM |
| Updating | Komponen diperbarui karena perubahan state atau props |
| Unmounting | Komponen dihapus dari DOM |
Analoginya seperti membuka aplikasi ride-hailing. Saat kamu buka Gojek, layar beranda mount ke tampilan. Saat kamu pilih destinasi, tampilan update. Saat kamu tutup aplikasi, semua komponen unmount dari memori.
Lifecycle di Class Components (Metode Klasik)
Sebelum Hooks hadir, React menggunakan Class Components untuk mengelola lifecycle. Ada beberapa metode lifecycle yang penting untuk dipahami:
1. componentDidMount — Setelah Komponen Muncul
Metode ini dipanggil sekali setelah komponen pertama kali muncul di DOM. Tempat terbaik untuk fetch data dari API.
import React, { Component } from 'react';
class ProfilPengguna extends Component {
constructor(props) {
super(props);
this.state = {
pengguna: null,
loading: true,
};
}
componentDidMount() {
// Dipanggil setelah komponen muncul di DOM
fetch('https://api.example.com/pengguna/1')
.then((res) => res.json())
.then((data) => {
this.setState({ pengguna: data, loading: false });
});
}
render() {
if (this.state.loading) return <p>Memuat data...</p>;
return <h1>Halo, {this.state.pengguna.nama}!</h1>;
}
}
export default ProfilPengguna;
2. componentDidUpdate — Setelah Komponen Diperbarui
Dipanggil setiap kali state atau props berubah. Hati-hati menggunakannya tanpa kondisi karena bisa menyebabkan infinite loop.
import React, { Component } from 'react';
class DetailProduk extends Component {
constructor(props) {
super(props);
this.state = { produk: null };
}
componentDidMount() {
this.fetchDataProduk(this.props.produkId);
}
componentDidUpdate(prevProps, prevState) {
// Cek apakah produkId berubah sebelum fetch ulang
if (prevProps.produkId !== this.props.produkId) {
this.fetchDataProduk(this.props.produkId);
}
}
fetchDataProduk(id) {
fetch(`https://api.example.com/produk/${id}`)
.then((res) => res.json())
.then((data) => this.setState({ produk: data }));
}
render() {
if (!this.state.produk) return <p>Memuat produk...</p>;
return (
<div>
<h2>{this.state.produk.nama}</h2>
<p>Harga: Rp {this.state.produk.harga.toLocaleString('id-ID')}</p>
</div>
);
}
}
export default DetailProduk;
3. componentWillUnmount — Sebelum Komponen Dihapus
Gunakan ini untuk membersihkan side effect: hapus timer, batalkan subscription, atau lepas event listener.
import React, { Component } from 'react';
class TimerBerjalan extends Component {
componentDidMount() {
this.timer = setInterval(() => {
console.log('Timer berjalan...');
}, 1000);
}
componentWillUnmount() {
// Bersihkan timer agar tidak terjadi memory leak
clearInterval(this.timer);
}
render() {
return <p>Timer sedang berjalan di background.</p>;
}
}
export default TimerBerjalan;
Lifecycle di Functional Components (Dengan useEffect)
Sejak React 16.8, cara modern mengelola lifecycle adalah dengan Hooks — khususnya useEffect. Satu hook ini bisa menggantikan componentDidMount, componentDidUpdate, dan componentWillUnmount sekaligus.
Sintaks Dasar useEffect
useEffect(() => {
// Kode yang dijalankan saat efek berlaku
return () => {
// Cleanup function (setara componentWillUnmount)
};
}, [dependencies]); // Array dependensi menentukan kapan efek dijalankan ulang
Meniru componentDidMount — Jalankan Sekali Saat Mount
Gunakan array dependensi kosong [] agar efek hanya berjalan satu kali saat komponen pertama kali muncul.
import React, { useState, useEffect } from 'react';
function DaftarArtikel() {
const [artikel, setArtikel] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Array kosong [] = hanya jalankan sekali saat mount
fetch('https://api.example.com/artikel')
.then((res) => res.json())
.then((data) => {
setArtikel(data);
setLoading(false);
});
}, []);
if (loading) return <p>Memuat artikel...</p>;
return (
<ul>
{artikel.map((item) => (
<li key={item.id}>{item.judul}</li>
))}
</ul>
);
}
export default DaftarArtikel;
Meniru componentDidUpdate — Jalankan Saat Dependensi Berubah
Masukkan nilai yang ingin dipantau ke dalam array dependensi. Efek akan berjalan ulang setiap kali nilai tersebut berubah.
import React, { useState, useEffect } from 'react';
function PencarianProduk({ keyword }) {
const [hasil, setHasil] = useState([]);
const [mencari, setMencari] = useState(false);
useEffect(() => {
// Dijalankan setiap kali `keyword` berubah
if (!keyword) return;
setMencari(true);
fetch(`https://api.example.com/produk?q=${keyword}`)
.then((res) => res.json())
.then((data) => {
setHasil(data);
setMencari(false);
});
}, [keyword]); // keyword sebagai dependensi
if (mencari) return <p>Mencari "{keyword}"...</p>;
return (
<div>
{hasil.length === 0 ? (
<p>Tidak ada hasil untuk "{keyword}"</p>
) : (
hasil.map((p) => <p key={p.id}>{p.nama}</p>)
)}
</div>
);
}
export default PencarianProduk;
Meniru componentWillUnmount — Cleanup Function
Return sebuah fungsi dari useEffect untuk melakukan pembersihan saat komponen unmount.
import React, { useState, useEffect } from 'react';
function Notifikasi() {
const [pesan, setPesan] = useState('');
useEffect(() => {
// Simulasi subscribe ke channel notifikasi
const handleNotifikasi = (data) => {
setPesan(data.teks);
};
window.addEventListener('notifikasi-masuk', handleNotifikasi);
return () => {
// Ini dijalankan saat komponen unmount — lepas event listener
window.removeEventListener('notifikasi-masuk', handleNotifikasi);
};
}, []); // Hanya mount/unmount, tidak ada dependensi
return (
<div>
<p>Mendengarkan notifikasi...</p>
{pesan && <p style={{ color: 'green' }}>Pesan baru: {pesan}</p>}
</div>
);
}
export default Notifikasi;
Contoh Kasus Nyata: Timer Countdown Flash Sale
Mari kita buat komponen yang menggabungkan ketiga fase lifecycle — sebuah timer countdown yang bisa dipakai di halaman flash sale (bayangkan kamu ingin membangun fitur seperti yang ada di e-commerce besar).
import React, { useState, useEffect } from 'react';
function TimerFlashSale({ durasiDetik }) {
const [sisaWaktu, setSisaWaktu] = useState(durasiDetik);
useEffect(() => {
// MOUNT: mulai interval saat komponen muncul
const interval = setInterval(() => {
setSisaWaktu((prev) => {
if (prev <= 1) {
clearInterval(interval);
return 0;
}
return prev - 1;
});
}, 1000);
// UNMOUNT: bersihkan interval saat komponen dihapus
return () => clearInterval(interval);
}, []); // Hanya jalankan sekali saat mount
const menit = Math.floor(sisaWaktu / 60);
const detik = sisaWaktu % 60;
return (
<div style={{ textAlign: 'center', padding: '1rem' }}>
<h2>Flash Sale Berakhir Dalam:</h2>
<p style={{ fontSize: '2.5rem', fontWeight: 'bold', color: '#e53e3e' }}>
{String(menit).padStart(2, '0')}:{String(detik).padStart(2, '0')}
</p>
{sisaWaktu === 0 && (
<p style={{ color: 'red', fontWeight: 'bold' }}>Flash sale telah berakhir!</p>
)}
</div>
);
}
export default TimerFlashSale;
// Cara pakai:
// <TimerFlashSale durasiDetik={300} /> → countdown 5 menit
Komponen ini menunjukkan ketiga fase lifecycle secara nyata:
- Mount: interval dimulai saat komponen pertama kali muncul
- Update:
sisaWaktuberkurang setiap detik → komponen re-render otomatis - Unmount: interval dibersihkan agar tidak bocor memori
Pemahaman tentang pola seperti ini juga erat kaitannya dengan bagaimana kamu menyusun kode JavaScript secara terstruktur — kamu bisa baca lebih lanjut di Mastering ES6 Modules: Cara Mengelola Kode JavaScript Secara Terstruktur.
Troubleshooting: Error yang Sering Muncul
Warning: Can’t perform a React state update on an unmounted component
Penyebab: Kamu melakukan setState setelah komponen sudah unmount, biasanya karena fetch data selesai setelah komponen dihapus dari DOM — misalnya pengguna berpindah halaman sebelum request API selesai.
Solusi:
import React, { useState, useEffect } from 'react';
function DataPengguna({ userId }) {
const [data, setData] = useState(null);
useEffect(() => {
let isMounted = true; // Flag untuk cek apakah komponen masih ada
fetch(`https://api.example.com/pengguna/${userId}`)
.then((res) => res.json())
.then((result) => {
if (isMounted) {
setData(result); // Hanya update jika komponen masih mounted
}
});
return () => {
isMounted = false; // Set false saat unmount
};
}, [userId]);
return <div>{data ? data.nama : 'Memuat...'}</div>;
}
export default DataPengguna;
useEffect Berjalan Terus-Menerus (Infinite Loop)
Penyebab: Kamu memasukkan objek atau array sebagai dependensi useEffect. Setiap render, objek baru dibuat dengan referensi berbeda sehingga React menganggap dependensi selalu berubah.
Solusi:
import React, { useState, useEffect, useMemo } from 'react';
function DaftarProduk({ kategori }) {
const [produk, setProduk] = useState([]);
// ❌ Salah — objek baru setiap render menyebabkan infinite loop
// useEffect(() => {
// fetchProduk({ kategori: kategori });
// }, [{ kategori: kategori }]);
// ✅ Benar — gunakan nilai primitif langsung sebagai dependensi
useEffect(() => {
fetch(`https://api.example.com/produk?kategori=${kategori}`)
.then((res) => res.json())
.then((data) => setProduk(data));
}, [kategori]); // string primitif — aman sebagai dependensi
return (
<ul>
{produk.map((p) => <li key={p.id}>{p.nama}</li>)}
</ul>
);
}
export default DaftarProduk;
componentDidMount Tidak Berjalan di Functional Component
Penyebab: Developer mencoba menggunakan metode class lifecycle (componentDidMount) di functional component — ini tidak bisa dilakukan karena sintaksnya berbeda total.
Solusi:
import React, { useEffect } from 'react';
// ❌ Salah — componentDidMount hanya ada di Class Component
// function KomponenSaya() {
// componentDidMount() { // SyntaxError!
// console.log('mounted');
// }
// }
// ✅ Benar — gunakan useEffect dengan array kosong
function KomponenSaya() {
useEffect(() => {
console.log('Komponen berhasil mount!');
// Lakukan fetch data, setup subscription, dll. di sini
}, []); // [] = hanya jalankan satu kali saat mount
return <div>Komponen saya sudah mount!</div>;
}
export default KomponenSaya;
Pertanyaan yang Sering Diajukan
Apa itu Component Lifecycle di React?
Component Lifecycle adalah serangkaian fase yang dilalui setiap komponen React sejak dibuat, diperbarui, hingga dihapus dari DOM. Tiga fase utamanya adalah mounting, updating, dan unmounting. Memahami lifecycle membantu kamu mengontrol kapan kode tertentu dijalankan dalam siklus hidup aplikasi.
Apa perbedaan useEffect dengan componentDidMount?
componentDidMount hanya tersedia di class component dan hanya berjalan sekali setelah mount. useEffect dengan array dependensi kosong [] memiliki perilaku serupa, tetapi tersedia di functional component dan jauh lebih fleksibel karena bisa menangani update dan cleanup dalam satu fungsi yang sama.
Mengapa cleanup function di useEffect itu penting?
Cleanup function mencegah memory leak — situasi di mana resource (seperti timer, subscription, atau event listener) terus berjalan meski komponennya sudah tidak ada di layar. Tanpa cleanup, aplikasimu bisa melambat atau bahkan crash setelah dipakai dalam waktu lama. Selalu tambahkan cleanup jika kamu membuat subscription atau timer di dalam useEffect.
Bagaimana cara menjalankan useEffect hanya saat props tertentu berubah?
Masukkan props tersebut ke dalam array dependensi useEffect. React akan membandingkan nilainya dengan render sebelumnya dan hanya menjalankan efek jika ada yang berubah.
useEffect(() => {
// Hanya berjalan saat `userId` berubah
fetchProfilPengguna(userId);
}, [userId]);
Apakah Class Component masih relevan digunakan di React modern?
Class Component masih didukung penuh dan tidak akan dihapus, tetapi komunitas React sangat merekomendasikan Functional Component + Hooks untuk kode baru. Functional Component lebih ringkas, lebih mudah di-test, dan lebih mudah dibaca terutama saat logika lifecycle menjadi kompleks.
Kesimpulan
Component Lifecycle adalah fondasi penting dalam React yang menentukan kapan dan bagaimana komponen berinteraksi dengan dunia luar — mulai dari fetch data, mengelola timer, hingga membersihkan resource saat komponen tidak diperlukan lagi.
Ringkasan yang perlu kamu ingat:
- Mounting → gunakan
useEffect(() => { ... }, [])untuk inisialisasi sekali - Updating → gunakan
useEffect(() => { ... }, [dependensi])untuk reaktivitas - Unmounting → kembalikan cleanup function dari
useEffectuntuk mencegah memory leak - Class Component menggunakan
componentDidMount,componentDidUpdate,componentWillUnmount - Functional Component modern cukup dengan
useEffectyang fleksibel
Selamat belajar dan terus berlatih! Konsep lifecycle mungkin terasa abstrak di awal, tapi semakin sering kamu mempraktikkannya di proyek nyata, semakin natural rasanya — dan kamu akan terkejut betapa banyak masalah performa bisa diselesaikan hanya dengan memahami lifecycle dengan baik. Jangan ragu untuk eksplorasi artikel-artikel lainnya di KamusNgoding, masih banyak topik seru yang menunggumu!