State adalah data yang bisa berubah selama aplikasi jalan, dan yang mengubah tampilan saat berubah. Di React Native caranya sama dengan React di web: useState, Context, atau library. Yang berbeda adalah HP-nya: aplikasi bisa ditinggal ke WhatsApp kapan saja, dan memorinya terbatas. Bayangin Mbak Wulan memotret papan order di toko Bu Rini. Foto itu salinan: kalau Bu Rini memindahkan kartu #13, fotonya gak ikut berubah, jadi harus dipotret ulang. Itu server state. Sementara pilihan "cuma lihat yang Disetrika" ada di HP Mbak Wulan saja, gak ada salinannya di mana pun. Itu client state.
🧪 Tebakan cepat: Akun, daftar order, dan filter disimpan di satu Context. Layar Pesanan punya empat komponen yang membaca Context itu: Sapaan, Ringkasan, FilterChips, dan DaftarOrder. Mbak Wulan mengetuk filter "Disetrika". Berapa dari empat komponen itu yang render ulang? Jawabannya ada di bagian "Context dan render ulang".
Server state dan client state
| Data | Jenis | Disimpan di |
|---|---|---|
| Daftar order #12, #13, #14 | server state: aslinya di server Bu Rini | TanStack Query |
| Filter status yang dipilih | client state, dipakai beberapa komponen | useState di induknya, atau Zustand |
| Isi kolom catatan untuk kurir | client state, satu komponen | useState |
| Akun yang sedang login | client state, dipakai di banyak layar | Context atau Zustand |
Server state bisa basi, perlu dimuat ulang, dan bisa gagal dimuat. Client state cuma berubah karena pengguna. Memisahkan keduanya membuat pilihan alatnya jelas. Sampai pelajaran API, ORDERS masih data contoh di src/data/orders.ts.
State lokal dan lifting state
// src/app/(tabs)/index.tsx
import { useState } from 'react';
import { FlatList, Pressable, Text, View } from 'react-native';
import { ORDERS, type Status } from '@/data/orders';
type Filter = 'Semua' | Status;
const PILIHAN: Filter[] = ['Semua', 'Antre', 'Dicuci', 'Disetrika', 'Siap diambil'];
function FilterChips({ aktif, onPilih }: { aktif: Filter; onPilih: (f: Filter) => void }) {
return (
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8, padding: 16 }}>
{PILIHAN.map((f) => (
<Pressable key={f} onPress={() => onPilih(f)}>
<Text style={{ fontWeight: f === aktif ? '700' : '400' }}>{f}</Text>
</Pressable>
))}
</View>
);
}
export default function Pesanan() {
const [filter, setFilter] = useState<Filter>('Semua');
const tampil = filter === 'Semua' ? ORDERS : ORDERS.filter((o) => o.status === filter);
return (
<View style={{ flex: 1 }}>
<FilterChips aktif={filter} onPilih={setFilter} />
<FlatList
data={tampil}
keyExtractor={(o) => o.id}
renderItem={({ item }) => <Text>#{item.id} {item.nama}: {item.status}</Text>}
ListEmptyComponent={<Text>Gak ada order dengan status {filter}.</Text>}
/>
</View>
);
}
FilterChipsdanFlatListbersaudara, dan dua-duanya butuhfilter. Jadi state-nya diangkat ke induk mereka,Pesanan, lalu turun lewat props. Itu lifting state.tampildihitung ulang dariORDERSdanfiltertiap render. Jangan simpan hasil filter di state kedua, nanti bisa beda dengan aslinya.
filter |
tampil |
|---|---|
'Semua' |
#12, #13, #14 |
'Disetrika' |
#13 Mbak Wulan |
'Antre' |
kosong: "Gak ada order dengan status Antre." |
Context dan render ulang
Kalau data yang sama dibutuhkan di banyak layar (akun di tab Pesanan dan tab Akun), mengoper props berlapis-lapis jadi repot. Context menyediakannya untuk semua komponen di bawah Provider:
// src/state/pesanan-context.tsx: SATU context untuk semuanya
type Nilai = { akun: string; orders: Order[]; filter: Filter; setFilter: (f: Filter) => void };
const PesananContext = createContext<Nilai | null>(null);
export function PesananProvider({ children }: { children: ReactNode }) {
const [filter, setFilter] = useState<Filter>('Semua');
return (
<PesananContext.Provider value={{ akun: 'Mbak Wulan', orders: ORDERS, filter, setFilter }}>
{children}
</PesananContext.Provider>
);
}
// di komponen mana pun di bawah Provider
const nilai = useContext(PesananContext); // null kalau di luar Provider
Semua komponen yang membaca PesananContext render ulang setiap kali value berubah, walaupun bagian yang dipakainya tetap. Object value dibuat baru di tiap render Provider. Hasil hitungan di Lab, setelah satu ketukan:
| Cara menyimpan | Ketuk filter "Disetrika" | Server: #13 jadi "Siap diambil" |
|---|---|---|
| Satu Context (akun, orders, filter) | Provider + 4 komponen | Provider + 4 komponen |
useState di Pesanan, tanpa memo |
Pesanan + 4 komponen |
Pesanan + 4 komponen |
useState di Pesanan, dengan memo |
Pesanan, FilterChips, DaftarOrder |
Pesanan, Ringkasan, DaftarOrder |
| Zustand (filter) + TanStack Query (orders) | FilterChips, DaftarOrder | Ringkasan, DaftarOrder |
Jadi dengan satu Context, keempatnya render ulang, termasuk Sapaan yang cuma membaca nama akun. Itu jawaban tebakannya. Template Expo SDK 57 menyalakan React Compiler (reactCompiler: true di app.json), yang otomatis me-memo komponen seperti baris ketiga. Tapi pembaca Context tetap ikut render ulang.
Zustand, Redux Toolkit, Jotai, dan TanStack Query
Versi di npm per 11 Oktober 2026: Zustand 5.0.15, Redux Toolkit 2.13.0 (dengan react-redux 9.3.0), Jotai 3.0.1, TanStack Query 5.104.1. Unduhan seminggu (2 sampai 8 Oktober, semua jenis project, bukan cuma React Native): TanStack Query 65,8 juta, Zustand 60,8 juta, Redux Toolkit 32,6 juta, Jotai 6,6 juta.
// src/state/filter.ts
import { create } from 'zustand';
import type { Status } from '@/data/orders';
type Filter = 'Semua' | Status;
export const useFilter = create<{ filter: Filter; setFilter: (filter: Filter) => void }>()((set) => ({
filter: 'Semua',
setFilter: (filter) => set({ filter }),
}));
// di komponen: render ulang cuma kalau filter berubah
const filter = useFilter((s) => s.filter);
// orders sebagai server state
import { useQuery } from '@tanstack/react-query';
async function ambilOrders(): Promise<Order[]> {
return ORDERS; // di pelajaran API, ini jadi fetch ke server
}
const { data: orders = [] } = useQuery({ queryKey: ['orders'], queryFn: ambilOrders });
- Zustand: store kecil tanpa Provider. Komponen memilih bagian yang dibutuhkan lewat selector, dan cuma render ulang saat bagian itu berubah.
- Redux Toolkit: satu store dengan slice, action, dan reducer. Lebih banyak struktur, DevTools untuk menelusuri tiap perubahan. Cocok untuk tim besar dan aturan yang rumit.
- Jotai: state dipecah jadi atom kecil, misalnya
const filterAtom = atom<Filter>('Semua'), dibaca denganuseAtom(filterAtom). - TanStack Query: bukan untuk client state, tapi untuk server state. Data disimpan per
queryKey, lengkap dengan status loading dan error. Ringkasan dan DaftarOrder memakai['orders']yang sama, jadi cuma ada satu request.
Background, foreground, dan memori
Di HP, aplikasi gak selalu di depan. AppState dari react-native melaporkan 'active' (di depan), 'background' (ditinggal ke aplikasi lain atau home screen), dan 'inactive' (iOS, sesaat, misalnya saat telepon masuk). Dua akibatnya:
- Saat di background, sistem boleh mematikan aplikasimu untuk mengambil memorinya. Semua state di memori hilang: dibuka lagi, filter kembali ke "Semua". Kalau harus bertahan, simpan ke penyimpanan HP. Itu bahasan pelajaran penyimpanan lokal.
- Saat kembali ke depan, daftar order mungkin sudah basi. TanStack Query memuat ulang saat fokus kembali, tapi di React Native gak ada event fokus jendela. Sambungkan sendiri:
// src/app/_layout.tsx, di dalam RootLayout (QueryClientProvider sudah dipasang)
import { focusManager } from '@tanstack/react-query';
import { useEffect } from 'react';
import { AppState } from 'react-native';
useEffect(() => {
const sub = AppState.addEventListener('change', (status) => {
focusManager.setFocused(status === 'active');
});
return () => sub.remove();
}, []);
Memori HP juga lebih sempit dari laptop. Simpan di state global yang memang dipakai bersama, bukan semua foto nota atau daftar ribuan order.
⚠️ Jebakan yang sering ketemu
- Satu Context raksasa untuk semuanya. Setiap perubahan membuat semua pembacanya render ulang. Pisahkan per kebutuhan, atau pakai store dengan selector.
- Menyalin data server ke
useStateatau Zustand. Salinannya basi begitu server berubah, dan kamu harus mengurus loading, error, dan muat ulang sendiri. Server state ke TanStack Query. - Menyimpan hasil hitungan di state kedua.
tampildihitung dariordersdanfilter. Kalau disimpan terpisah, cepat atau lambat keduanya gak cocok. - Mengira state bertahan setelah aplikasi ditutup. State di memori hilang saat sistem mematikan aplikasi di background. Simpan yang penting ke penyimpanan HP.
TL;DR: Pisahkan server state (daftar order, aslinya di server) dari client state (filter, isian form). State yang dipakai satu komponen: useState. Dipakai saudara: angkat ke induknya (lifting state). Dipakai di banyak layar: Context, tapi semua pembaca Context render ulang setiap value berubah (di Lab: Provider + 4 komponen untuk satu ketukan filter). Zustand dengan selector cuma merender yang membaca bagian yang berubah (2 komponen). Library populer: Zustand 5, Redux Toolkit 2 (struktur dan DevTools), Jotai 3 (atom), dan TanStack Query 5 untuk server state. Di HP, aplikasi bisa ke background dan dimatikan sistem (state di memori hilang), dan memorinya terbatas. Sambungkan AppState ke focusManager supaya order dimuat ulang saat aplikasi kembali ke depan.