Selama saya bekerja menggunakan React, saya telah beberapa kali mengubah paradigma bagaimana menghubungkan component dengan data eksternal:
Consume pada Root Component, lalu turunkan melalui props
Ini adalah cara pertama yang saya pelajari karena pada awal saya belajar React, saya dikenalkan dengan metodologi Atomic Design.
Disclaimer: Metodologi ini sama sekali tidak mengatur detail implementasi tentang bagaimana component dibuat pada React. Jadi ini murni hanya interpretasi saya saat mempelajari Atomic Design.
Saat mempelajari metodologi tersebut, tertanam dalam mindset saya bahwa baiknya component tidak langsung memiliki data tersendiri, melainkan diisi oleh component terbesar dan terluar, yaitu Page, sehingga component tersebut benar-benar dapat digunakan ulang, dan cukup mengubah sumber data dari satu tempat.
// Page
export function DashboardPage() {
const metrics = useMetricsData();
const user = useUserData();
return <DashboardTemplate metrics={metrics} user={user} />;
}
// Template
export function DashboardTemplate({ metrics, user }) {
return (
<main>
<Navbar user={user} />
<StatsCard title="Page Views" amount={metrics.pageView} />;
<StatsCard title="Bounce Rate" amount={metrics.bounceRate} />;
</main>
);
}
Jadi consume data eksternal hanya terjadi di dalam Page, sedangkan Template dan component di dalamnya cukup menerima data tersebut melalui props. Tidak ada component yang boleh memanggil useMetricsData
atau pun useUserData
selain Page.
Kelebihan
- Seluruh component menjadi terisolasi dan reusable, karena tidak terikat dengan data eksternal tertentu
- Lebih mudah untuk di-test, karena data pada component dapat diisi cukup menggunakan props, dan tidak perlu melakukan mocking atau membuat custom wrapper
Kekurangan
- Terjadi prop drilling, yang berarti kita perlu menurunkan nilai tersebut ke component-nya satu per satu hingga component terdalam
- Terjadi re-render pada seluruh component ketika data eksternal berubah
Consume langsung pada component, pindah ke luar jika diperlukan saja
Setelah mengalami kesengsaraan menurunkan nilai ke props pada component yang berlapis-lapis, dan ternyata juga cukup berisiko pada performa aplikasi, saya memutuskan untuk memindahkan consume data dari Root component ke component yang membutuhkannya langsung.
export function Dashboard() {
// Consume di sini karena data `metrics` digunakan oleh lebih dari satu component
const metrics = useMetricsData();
return (
<main>
<Navbar />
<StatsCard title="Page Views" amount={metrics.pageView} />;
<StatsCard title="Bounce Rate" amount={metrics.bounceRate} />;
</main>
);
}
export function Navbar() {
// Consume langsung di sini karena pada Dashboard hanya component ini yang perlu data `user`
const user = useUserData();
return (
<nav>
Hello {user}!
</nav>
);
}
Kelebihan
- Tidak ada lagi prop drilling yang tidak diperlukan
- Component tidak melakukan re-render yang tidak perlu
Kekurangan
- Component tidak reusable secara default. Sehingga jika suatu saat component diperlukan untuk menampilkan data yang berbeda, maka akan perlu dilakukan refactoring
- Component jadi lebih sulit untuk di-test
Setelah sekian lama menggunakan cara kedua, saya menemukan cara yang lebih baik:
Membuat wrapper untuk consume data eksternal
Cara ini saya temukan setelah merasakan pain point saat melakukan testing component; mocking data eksternal. Terlebih lagi pada masih fase awal pengembangan, yang dimana data eksternal ini masih sangat mudah berubah bentuk, sedangkan props component tidak.
Dari situ saya terpikir, component yang mau saya test saja tidak berubah, kenapa saya harus capek-capek revisi mock-nya?
Akhirnya saya memutuskan untuk memisahkan consume data eksternal ke dalam komponen tersendiri.
export function DashboardLoader() {
const metrics = useMetricsData();
return <Dashboard metrics={metrics} />
}
import { NavbarLoader as Navbar } from './Navbar';
export function Dashboard({ metrics }) {
return (
<main>
<Navbar />
<StatsCard title="Page Views" amount={metrics.pageView} />;
<StatsCard title="Bounce Rate" amount={metrics.bounceRate} />;
</main>
);
}
export function NavbarLoader() {
const user = useUserData();
return <Navbar user={user} />
}
Kelebihan
- Seluruh component menjadi reusable
- Component mudah di-test
- Tidak terjadi prop drilling
Kekurangan
- Menulis sedikit lebih banyak untuk membuat wrapper-nya
- Pada kasus tertentu, terjadi rerender pada component yang tidak berkaitan. Seperti pada contoh di atas,
Navbar
akan rerender ketikametrics
berubah
Begitulah alasan mengapa sebaiknya tidak menempatkan consume data eksternal, entah itu API call, global state, dlsb, pada component-nya langsung.
Namun, jika terdapat cara yang lebih baik, saya dengan senang hati untuk berubah pikiran, karena di balik programmer yang bahagia ada kode yang bersih.
Terima kasih sudah membaca.
Discussion