Local Storage (AsyncStorage, MMKV) — Mobile

Persistent Storage di Mobile Mobile app butuh menyimpan data lokal — user preferences, cached data, auth tokens. Tidak ada localStorage seperti di browser…

Penyimpanan lokal adalah tempat aplikasi menaruh data di HP supaya data itu masih ada setelah aplikasinya ditutup. Di pelajaran state kamu lihat sendiri: filter "Disetrika" hilang begitu sistem mematikan aplikasi di background. Bayangin meja kasir Bu Rini. Ada buku catatan di laci (harus dibuka dulu, sebentar, baru terbaca), papan tulis kecil di dinding (sekali lirik langsung terbaca), brankas untuk kunci toko, dan lemari arsip berlaci untuk ribuan nota. Di HP, keempatnya adalah AsyncStorage, MMKV, SecureStore, dan SQLite.

🧪 Tebakan cepat: Filter "Disetrika" disimpan dengan Zustand persist ke AsyncStorage. Mbak Wulan menutup aplikasi lalu membukanya lagi. Di render pertama, chip filter mana yang aktif? Jawabannya ada di bagian "Menyimpan filter: AsyncStorage dan MMKV".

Empat tempat menyimpan di HP

Versi yang dipasang npx expo install di Expo SDK 57 (dicek 11 Oktober 2026):

Penyimpanan Versi Cara baca Expo Go Cocok untuk
@react-native-async-storage/async-storage 2.2.0 async: await getItem(key) ada pengaturan kecil, cache kecil
react-native-mmkv + react-native-nitro-modules 4.3.2 + 0.37.1 sync: getString(key) gak ada pengaturan yang dibaca saat start
expo-secure-store 57.0.4 async, terenkripsi ada token login, rahasia kecil
expo-sqlite 57.0.4 query SQL ada data banyak yang dicari dan disaring

Menyimpan filter: AsyncStorage dan MMKV

// src/state/filter.ts
import AsyncStorage from '@react-native-async-storage/async-storage';
import { create } from 'zustand';
import { createJSONStorage, persist } from 'zustand/middleware';
import type { Status } from '@/data/orders';

type Filter = 'Semua' | Status;

export const useFilter = create<{ filter: Filter; setFilter: (filter: Filter) => void }>()(
  persist(
    (set) => ({ filter: 'Semua', setFilter: (filter) => set({ filter }) }),
    { name: 'filter-pesanan', storage: createJSONStorage(() => AsyncStorage) },
  ),
);

Setiap setFilter('Disetrika') menulis kunci filter-pesanan berisi {"state":{"filter":"Disetrika"},"version":0}. Fungsi gak ikut tersimpan, karena JSON.stringify melewatinya. Versi MMKV cuma mengganti storage:

import { createMMKV } from 'react-native-mmkv';
import type { StateStorage } from 'zustand/middleware';

const mmkv = createMMKV();
const penyimpanan: StateStorage = {
  getItem: (key) => mmkv.getString(key) ?? null,
  setItem: (key, value) => mmkv.set(key, value),
  removeItem: (key) => { mmkv.remove(key); },
};
// ...lalu storage: createJSONStorage(() => penyimpanan)

Yang terjadi saat aplikasi dibuka lagi:

Saat AsyncStorage MMKV
store dibuat getItem mengembalikan Promise, belum selesai getString langsung mengembalikan JSON-nya
render pertama filter 'Semua': chip Semua menyala, daftar belum disaring filter 'Disetrika': chip Disetrika sudah menyala
Promise selesai filter jadi 'Disetrika', render ulang gak ada perubahan

Jadi dengan AsyncStorage, render pertama masih 'Semua', lalu sekejap kemudian berganti ke "Disetrika": daftarnya berkedip. Itu jawaban tebakannya. Zustand cuma bisa memulihkan state secara synchronous kalau penyimpanannya synchronous. Pilihannya: MMKV, atau tahan splash screen sampai useFilter.persist.hasHydrated() bernilai true.

Daftar order untuk mulai cepat: persist TanStack Query

Daftar order adalah server state, jadi yang disimpan adalah cache TanStack Query, lewat @tanstack/react-query-persist-client dan @tanstack/query-async-storage-persister (5.104.1):

// src/app/_layout.tsx (bagian penyedia query)
import AsyncStorage from '@react-native-async-storage/async-storage';
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';
import { QueryClient } from '@tanstack/react-query';
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';

const queryClient = new QueryClient({
  defaultOptions: { queries: { gcTime: 1000 * 60 * 60 * 24 } }, // 24 jam, sama dengan maxAge
});
const persister = createAsyncStoragePersister({ storage: AsyncStorage });

// di RootLayout: bungkus <Stack /> dengan
<PersistQueryClientProvider client={queryClient} persistOptions={{ persister, maxAge: 1000 * 60 * 60 * 24 }}>

Yang gak boleh disimpan polos

File AsyncStorage dan MMKV adalah teks polos di folder aplikasi. Aplikasi lain gak bisa membacanya, tapi HP yang di-root dan build debug bisa.

⚠️ Jebakan yang sering ketemu

TL;DR: AsyncStorage (2.2.0 di SDK 57) menyimpan string tanpa enkripsi lewat Promise, ada di Expo Go, dan dibatasi 6 MB di Android. MMKV 4 (createMMKV(), butuh react-native-nitro-modules dan development build) membaca secara synchronous lewat JSI, sekitar 30 kali lebih cepat menurut README-nya, jadi filter yang disimpan sudah benar di render pertama, sementara AsyncStorage menampilkan 'Semua' dulu lalu berkedip ke "Disetrika". SecureStore (Keychain dan Keystore) cuma untuk rahasia kecil seperti token. expo-sqlite untuk data banyak, dengan kv-store sebagai pengganti AsyncStorage. Untuk mulai cepat, simpan cache TanStack Query dengan PersistQueryClientProvider dan createAsyncStoragePersister (maxAge 24 jam, gcTime minimal sama). Jangan simpan token, password, PIN, atau data identitas tanpa enkripsi, dan bersihkan cache saat logout.

Yang akan kamu pelajari