Atom-based State Management dengan Recoil/Jotai: Membangun Aplikasi React yang Cerdas dan Skalabel
1. Pendahuluan
Jika Anda seorang developer React, Anda pasti akrab dengan tantangan dalam mengelola state aplikasi. Mulai dari “prop drilling” yang membuat kode berantakan, boilerplate yang banyak di Redux, hingga keterbatasan Context API untuk skenario kompleks dengan pembaruan yang sering. Seiring bertambahnya fitur dan skala aplikasi, state management bisa menjadi “neraka” yang memengaruhi performa dan developer experience (DX).
Untungnya, ekosistem React terus berinovasi. Dua library yang semakin populer dan menawarkan paradigma baru untuk state management adalah Recoil (dari Facebook) dan Jotai (lebih minimalis). Keduanya mengusung konsep atom-based state management, sebuah pendekatan yang terinspirasi langsung dari cara kerja React itu sendiri. Mereka memungkinkan Anda mengelola state dengan granularitas tinggi, performa optimal, dan kode yang lebih bersih.
Artikel ini akan membawa Anda menyelami dunia atom-based state management. Kita akan memahami apa itu atom, selector, mengapa pendekatan ini begitu powerful, dan bagaimana Anda bisa mulai menggunakannya untuk membangun aplikasi React yang lebih cerdas dan skalabel.
2. Apa Itu Atom-based State Management?
Bayangkan state aplikasi Anda sebagai sekumpulan butiran pasir yang sangat kecil, bukan satu bongkahan besar. Setiap butiran pasir (atom) adalah unit state independen yang dapat dibaca dan diperbarui secara terpisah.
Tradisionalnya, banyak solusi state management cenderung mengumpulkan seluruh state aplikasi ke dalam satu objek besar (seperti di Redux Store) atau beberapa objek besar. Ketika sebagian kecil dari state itu berubah, seringkali memicu render ulang yang tidak perlu pada banyak komponen karena mereka “mendengarkan” perubahan pada objek state yang lebih besar.
Atom-based state management mengubah perspektif ini. Daripada mengelola satu global store yang monolitik, kita mendefinisikan state dalam unit-unit yang sangat kecil dan terisolasi, yang disebut atom.
📌 Analogi: Jika aplikasi Anda adalah sebuah rumah, state di global store tradisional seperti satu saklar lampu utama yang mengontrol semua lampu di rumah. Ketika Anda ingin menyalakan lampu dapur, Anda harus menyalakan saklar utama, dan itu memicu perubahan pada semua lampu lain (meskipun mereka tidak berubah).
Dengan atom-based state management, setiap lampu memiliki saklarnya sendiri (atom). Ketika Anda menyalakan lampu dapur, hanya lampu dapur yang terpengaruh, tanpa memengaruhi atau memicu “pemeriksaan” pada lampu di kamar tidur atau ruang tamu. Ini jauh lebih efisien!
Setiap komponen kemudian dapat “berlangganan” (subscribe) hanya pada atom-atom spesifik yang mereka butuhkan. Jika sebuah atom berubah, hanya komponen yang berlangganan atom tersebut yang akan mengalami re-render. Ini adalah kunci untuk performa yang lebih baik dan re-render yang lebih efisien di aplikasi React Anda.
3. Konsep Dasar: Atom dan Selector
Dua pilar utama dalam atom-based state management adalah atom dan selector.
3.1. Atom: Unit State Terkecil
Atom adalah unit state yang dapat di-subscribe oleh komponen dan juga diperbarui. Atom adalah sumber kebenaran (source of truth) untuk state tertentu. Mereka mirip dengan state lokal dalam useState React, tetapi dengan kemampuan untuk diakses dan dibagi di mana saja dalam pohon komponen Anda.
Contoh dengan Jotai:
// atoms.js
import { atom } from 'jotai';
// Atom untuk menyimpan nilai counter
export const countAtom = atom(0);
// Atom untuk menyimpan status dark mode
export const darkModeAtom = atom(false);
Untuk menggunakan atom ini di komponen React Anda:
// MyCounter.jsx
import React from 'react';
import { useAtom } from 'jotai';
import { countAtom } from './atoms';
function MyCounter() {
const [count, setCount] = useAtom(countAtom); // Menggunakan atom countAtom
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Tambah</button>
<button onClick={() => setCount(c => c - 1)}>Kurang</button>
</div>
);
}
export default MyCounter;
✅ Keunggulan Atom:
- Granularitas: Setiap atom adalah state mandiri.
- Akses Global: Dapat diakses oleh komponen manapun tanpa prop drilling.
- Efisiensi: Hanya komponen yang menggunakan atom tersebut yang akan re-render saat atom berubah.
3.2. Selector: Derived State yang Fleksibel
Selector adalah fungsi pure yang mengambil state dari satu atau lebih atom (atau selector lain) dan melakukan transformasi data. Mereka memungkinkan Anda untuk mendapatkan derived state (state turunan) tanpa perlu menyimpan state tersebut secara eksplisit. Selector akan secara otomatis re-evaluate dan membuat komponen yang berlangganan untuk re-render hanya ketika atom atau selector yang menjadi dependensinya berubah.
Contoh dengan Jotai:
// atoms.js (lanjutan)
import { atom } from 'jotai';
export const countAtom = atom(0);
export const darkModeAtom = atom(false);
// Selector untuk mendapatkan status apakah counter genap atau ganjil
export const isEvenCountSelector = atom((get) => get(countAtom) % 2 === 0);
// Selector untuk mendapatkan teks sambutan berdasarkan dark mode
export const greetingSelector = atom((get) =>
get(darkModeAtom) ? 'Selamat malam!' : 'Selamat pagi!'
);
Untuk menggunakan selector ini di komponen React Anda:
// MyStatus.jsx
import React from 'react';
import { useAtomValue } from 'jotai'; // useAtomValue hanya untuk membaca
import { isEvenCountSelector, greetingSelector } from './atoms';
function MyStatus() {
const isEven = useAtomValue(isEvenCountSelector);
const greeting = useAtomValue(greetingSelector);
return (
<div>
<p>{greeting}</p>
<p>Count is: {isEven ? 'Genap' : 'Ganjil'}</p>
</div>
);
}
export default MyStatus;
💡 Keunggulan Selector:
- Derived State: Membuat state turunan yang kompleks dari state dasar secara efisien.
- Caching: Hasil selector di-cache dan hanya dihitung ulang jika dependensinya berubah, menghemat komputasi.
- Komposisi: Selector dapat mengambil input dari selector lain, memungkinkan komposisi logika yang kuat.
4. Mengapa Menggunakan Recoil/Jotai? Keunggulan Utama
Setelah memahami dasar-dasarnya, mari kita gali lebih dalam mengapa pendekatan atom-based ini bisa menjadi game changer untuk aplikasi React Anda:
-
Granularitas dan Performa Optimal:
❌ Masalah Tradisional: Dengan Context API atau Redux, seringkali seluruh subtree komponen akan re-render bahkan jika hanya sebagian kecil state yang berubah.✅ Solusi Atom-based: Hanya komponen yang secara eksplisit berlangganan pada atom atau selector yang berubah akan di-re-render. Ini meminimalkan pekerjaan yang tidak perlu dan menghasilkan aplikasi yang jauh lebih cepat dan responsif.
-
Fleksibilitas dan Skalabilitas:
- Mudah untuk membuat atom baru dan menggabungkannya sesuai kebutuhan. Anda tidak perlu memikirkan struktur global store yang kaku sejak awal.
- Cocok untuk aplikasi besar dengan banyak fitur yang berbeda, karena setiap fitur dapat memiliki state atomnya sendiri tanpa memengaruhi fitur lain.
-
Derived State yang Kuat dan Efisien:
- Selector adalah cara elegan untuk mengelola logika bisnis yang kompleks yang bergantung pada berbagai bagian state.
- Dengan fitur caching bawaan, Anda mendapatkan performa tanpa harus memikirkan memoization manual seperti
useMemoatauuseCallbacksecara berlebihan untuk state turunan.
-
Integrasi Native dengan React:
- API Recoil dan Jotai terasa seperti ekstensi alami dari React Hooks (
useState,useContext). Ini membuat learning curve lebih landai bagi developer React. - Dirancang untuk bekerja dengan fitur-fitur modern React seperti Concurrent Mode dan Suspense, memungkinkan pengalaman pengguna yang lebih mulus (misalnya, loading state otomatis saat fetching data asinkron).
- API Recoil dan Jotai terasa seperti ekstensi alami dari React Hooks (
-
Boilerplate Minimal:
- Dibandingkan dengan Redux yang seringkali membutuhkan reducer, action creator, middleware, dan selector yang terpisah, Recoil/Jotai seringkali lebih ringkas dan langsung ke inti masalah. Anda dapat mendefinisikan state dan logikanya di satu tempat.
5. Recoil vs. Jotai: Memilih yang Tepat
Baik Recoil maupun Jotai sama-sama mengimplementasikan filosofi atom-based state management. Namun, ada beberapa perbedaan yang bisa menjadi pertimbangan Anda:
-
Recoil:
- Dikembangkan oleh Facebook, tim yang sama di balik React. Ini memberikan tingkat kepercayaan tertentu terhadap kompatibilitas dan arah pengembangan di masa depan.
- Menyediakan lebih banyak fitur bawaan seperti snapshotting, transactional updates, dan persisted state (walaupun beberapa masih eksperimental atau membutuhkan add-on).
- API-nya sedikit lebih “berisi” dan eksplisit, misalnya dengan
recoil-rootyang harus membungkus aplikasi Anda.
-
Jotai:
- Filosofi “primitive” dan “minimalis”. Jotai bertujuan untuk menjadi state management yang paling kecil dan sederhana, sering disebut “less code, more state”.
- Tidak memerlukan provider pembungkus di level root (kecuali untuk fitur tertentu seperti persisted state).
- Ukuran bundle-nya sangat kecil.
- Sangat fleksibel dan dapat dikombinasikan dengan library lain dengan mudah.
Kapan memilih yang mana?
- Pilih Recoil jika Anda menginginkan solusi yang didukung oleh tim React, dengan fitur-fitur canggih bawaan yang mungkin Anda butuhkan di masa depan, dan Anda tidak keberatan dengan sedikit overhead dalam ukuran bundle atau konfigurasi awal.
- Pilih Jotai jika Anda mengutamakan ukuran bundle yang sangat kecil, minimalisme, fleksibilitas, dan Anda lebih suka membangun fitur tambahan sendiri di atas primitive yang disediakan. Jotai sering menjadi pilihan favorit untuk proyek yang ingin menjaga footprint sekecil mungkin atau ketika Anda ingin mengintegrasikan dengan library lain yang sudah ada.
Keduanya adalah pilihan yang sangat baik dan akan memberikan manfaat performa dan DX yang serupa. Pilihan seringkali tergantung pada preferensi pribadi dan kebutuhan spesifik proyek.
6. Contoh Praktis: Aplikasi Counter Sederhana dengan Jotai
Mari kita buat contoh sederhana menggunakan Jotai untuk melihat bagaimana atom dan selector bekerja dalam praktik.
Pertama, pastikan Anda sudah menginstal Jotai:
npm install jotai
# atau
yarn add jotai
6.1. Mendefinisikan Atom dan Selector
Buat file src/store/atoms.js:
// src/store/atoms.js
import { atom } from 'jotai';
// Atom untuk nilai counter
export const countAtom = atom(0);
// Atom untuk status apakah counter aktif (misal: lebih dari 0)
export const isActiveAtom = atom((get) => get(countAtom) > 0);
// Selector untuk nilai counter yang dikalikan dua
export const doubledCountSelector = atom((get) => get(countAtom) * 2);
// Selector untuk status apakah counter genap atau ganjil
export const paritySelector = atom((get) =>
get(countAtom) % 2 === 0 ? 'Genap' : 'Ganjil'
);
// Atom asinkron untuk mengambil data dari API (contoh)
export const usersAtom = atom(async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await response.json();
return data;
});
// Selector untuk mendapatkan jumlah user
export const userCountSelector = atom((get) => get(usersAtom).length);
6.2. Menggunakan Atom dan Selector di Komponen
Buat komponen CounterDisplay.jsx:
// src/components/CounterDisplay.jsx
import React from 'react';
import { useAtomValue } from 'jotai';
import { countAtom, doubledCountSelector, paritySelector } from '../store/atoms';
function CounterDisplay() {
const count = useAtomValue(countAtom); // Membaca nilai atom
const doubledCount = useAtomValue(doubledCountSelector); // Membaca nilai selector
const parity = useAtomValue(paritySelector); // Membaca nilai selector
return (
<div style={{ border: '1px solid #ccc', padding: '15px', margin: '10px', borderRadius: '5px' }}>
<h3>Display Counter</h3>
<p>Current Count: **{count}**</p>
<p>Doubled Count: **{doubledCount}**</p>
<p>Parity: **{parity}**</p>
</div>
);
}
export default CounterDisplay;
Buat komponen CounterControls.jsx:
// src/components/CounterControls.jsx
import React from 'react';
import { useAtom, useSetAtom } from 'jotai';
import { countAtom, isActiveAtom } from '../store/atoms';
function CounterControls() {
const [count, setCount] = useAtom(countAtom); // Membaca dan menulis atom
const isActive = useAtomValue(isActiveAtom); // Membaca nilai selector
const increment = () => setCount((prev) => prev + 1);
const decrement = () => setCount((prev) => prev - 1);
const reset = () => setCount(0);
return (
<div style={{ border: '1px solid #007bff', padding: '15px', margin: '10px', borderRadius: '5px' }}>
<h3>Counter Controls</h3>
<p>Status: {isActive ? 'Aktif' : 'Tidak Aktif'}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
}
export default CounterControls;
Buat komponen UserFetcher.jsx:
// src/components/UserFetcher.jsx
import React, { Suspense } from 'react';
import { useAtomValue } from 'jotai';
import { usersAtom, userCountSelector } from '../store/atoms';
function UserList() {
const users = useAtomValue(usersAtom);
const userCount = useAtomValue(userCountSelector);
return (
<div>
<h4>Daftar Pengguna ({userCount} total)</h4>
<ul>
{users.map(user => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
</div>
);
}
function UserFetcher() {
return (
<div style={{ border: '1px solid #28a745', padding: '15px', margin: '10px', borderRadius: '5px' }}>
<h3>Fetch Data Asynchronous</h3>
<Suspense fallback={<div>Loading users...</div>}>
<UserList />
</Suspense>
</div>
);
}
export default UserFetcher;
Gabungkan di App.js:
// src/App.js
import React from 'react';
import CounterDisplay from './components/CounterDisplay';
import CounterControls from './components/CounterControls';
import UserFetcher from './components/UserFetcher';
function App() {
return (
<div style={{ fontFamily: 'Arial, sans-serif', padding: '20px' }}>
<h1>Aplikasi Jotai Sederhana</h1>
<CounterControls />
<CounterDisplay />
<UserFetcher />
</div>
);
}
export default App;
Penjelasan:
CounterControlsdanCounterDisplaysama-sama menggunakancountAtom. KetikaCounterControlsmengubahcountAtom, hanyaCounterDisplaydanCounterControlsitu sendiri (karena mereka berlangganan) yang akan re-render.doubledCountSelectordanparitySelectorsecara otomatis dihitung ulang ketikacountAtomberubah, dan hanyaCounterDisplayyang akan re-render karena ia menggunakan selector tersebut.UserFetchermenunjukkan bagaimana atom asinkron bekerja dengan React Suspense, menyediakan pengalaman loading yang otomatis.
Ini adalah contoh sederhana, tetapi menunjukkan bagaimana atom-based state management memungkinkan Anda untuk mendefinisikan state secara granular dan mengelolanya dengan cara yang modular dan efisien.
Kesimpulan
Atom-based state management dengan library seperti Recoil dan Jotai menawarkan pendekatan yang menyegarkan untuk mengelola state di aplikasi React. Dengan fokus pada granularitas, derived state yang efisien, dan integrasi native dengan React, mereka memecahkan banyak masalah yang sering ditemui dalam solusi state management tradisional.
Jika Anda mencari cara untuk meningkatkan performa aplikasi React Anda, mengurangi boilerplate, dan meningkatkan developer experience, maka mempelajari dan mengadopsi Recoil atau Jotai bisa menjadi langkah yang sangat tepat. Cobalah sendiri dan rasakan perbedaannya dalam membangun aplikasi React yang lebih cerdas, lebih cepat, dan lebih mudah dikelola.
🔗 Baca Juga
- Modern Frontend State Management: Memilih dan Mengelola State di Aplikasi Web Skala Besar
- Zustand: State Management Simpel dan Kuat untuk Aplikasi React Modern
- Menggali Lebih Dalam React Hooks: Panduan Praktis untuk Developer Modern
- Membangun Aplikasi React yang Tangguh: Panduan Unit dan Integration Testing dengan React Testing Library