State Management di Mobile — Mobile

State Management di React Native Konsep state management di React Native sama dengan React web. Yang berbeda hanyalah pertimbangan performa karena resource…

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>
  );
}
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 });

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:

// 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

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.

Yang akan kamu pelajari