Akar Masalah: Latensi Asynchronous State Hydration

UI flickering atau flash warna tema saat cold boot terjadi karena perbedaan waktu antara siklus inisialisasi JavaScript runtime dan pembacaan persistent storage. Pada arsitektur bawaan React Native yang menggunakan @react-native-async-storage/async-storage, pembacaan data berjalan secara asynchronous melalui event loop microtask atau bridge serialization.

Ketika aplikasi pertama kali melakukan mounting tree komponen:

  1. State container (Zustand, Redux, atau React Context) diinisialisasi dengan state default (misal: theme: 'light' atau isAuthenticated: false).
  2. React merender frame pertama (Frame 0) menggunakan nilai default tersebut. Layar menampilkan UI bertema terang atau merender flow autentikasi login.
  3. Promise pembacaan storage selesai dievaluasi beberapa tick setelahnya. State di-hydrate dengan nilai asli yang tersimpan (misal: tema pengguna adalah dark atau pengguna sudah login).
  4. React memicu re-render dan mengganti layout atau tema secara instan. Perubahan mendadak dari layout putih ke gelap atau perpindahan layar login ke dashboard menghasilkan UI flash yang tampak jelas oleh pengguna.

Solusi 1: Synchronous Hydration via JSI dengan MMKV

Untuk mengeliminasi latency rendering pada frame awal, persistent store harus dapat diakses secara synchronous sebelum siklus render pertama dieksekusi. Library react-native-mmkv memanfaatkan JavaScript Interface (JSI), memungkinkan thread JavaScript berkomunikasi langsung dengan memori native C++ tanpa serialization overhead dan tanpa Promise.

MMKV menggunakan memory-mapped files (mmap) dari OS, memastikan data langsung tersedia di memori virtual proses saat runtime React Native membaca storage.

Implementasi Zustand Persist dengan Engine MMKV

Konfigurasi StateStorage kustom pada Zustand menggunakan MMKV secara synchronous. Dengan skema ini, state terisi secara sinkron saat store diinisialisasi di root module level sebelum komponen React selesai dimuat.

// storage.ts
import { MMKV } from 'react-native-mmkv';
import { StateStorage } from 'zustand/middleware';

export const storage = new MMKV({
  id: 'critical-storage',
  encryptionKey: 'optional-encryption-key',
});

export const mmkvStateStorage: StateStorage = {
  setItem: (name: string, value: string) => {
    storage.set(name, value);
  },
  getItem: (name: string) => {
    const value = storage.getString(name);
    return value ?? null;
  },
  removeItem: (name: string) => {
    storage.delete(name);
  },
};

Store Zustand dikonfigurasi dengan middleware persist menggunakan storage synchronous tersebut:

// useBootstrapStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { mmkvStateStorage } from './storage';

interface BootstrapState {
  theme: 'light' | 'dark';
  authToken: string | null;
  isHydrated: boolean;
  setTheme: (theme: 'light' | 'dark') => void;
  setAuthToken: (token: string | null) => void;
}

export const useBootstrapStore = create<BootstrapState>()(
  persist(
    (set) => ({
      theme: 'light',
      authToken: null,
      isHydrated: false,
      setTheme: (theme) => set({ theme }),
      setAuthToken: (authToken) => set({ authToken }),
    }),
    {
      name: 'bootstrap-state',
      storage: createJSONStorage(() => mmkvStateStorage),
      onRehydrateStorage: () => (state) => {
        if (state) {
          state.isHydrated = true;
        }
      },
    }
  )
);

Catatan Eksekusi: Karena getItem dieksekusi secara synchronous melalui MMKV, state theme dan authToken sudah berada pada nilai persisten pada siklus evaluasi modul pertama, mencegah layout frame pertama merender fallback default.

Solusi 2: Koordinasi Native Splash Screen (react-native-bootsplash)

Meskipun data tersimpan dibaca secara instan melalui MMKV, beberapa proses inisialisasi—seperti validasi integritas token ke keychain atau penyiapan navigasi—mungkin tetap memerlukan operasi asynchronous tambahan. Untuk kasus ini, tahan native splash screen di native window layer sampai rendering pertama siap.

// App.tsx
import React, { useEffect } from 'react';
import { View, StyleSheet } from 'react-native';
import BootSplash from 'react-native-bootsplash';
import { useBootstrapStore } from './useBootstrapStore';

export default function App() {
  const isHydrated = useBootstrapStore((state) => state.isHydrated);
  const theme = useBootstrapStore((state) => state.theme);

  useEffect(() => {
    if (isHydrated) {
      // Hilangkan native bootsplash setelah tree siap
      BootSplash.hide({ fade: true });
    }
  }, [isHydrated]);

  if (!isHydrated) {
    return null;
  }

  return (
    <View style={[styles.container, theme === 'dark' ? styles.dark : styles.light]}>
      {/* Root Navigation */}
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  dark: { backgroundColor: '#121212' },
  light: { backgroundColor: '#FFFFFF' },
});

Arsitektur State: Pemisahan Critical vs Deferred State

Jangan menyimpan seluruh persistent data aplikasi ke dalam storage synchronous. Praktik terbaik membagi storage ke dalam dua kategori:

  • Critical Bootstrapping State (MMKV Synchronous): Ukuran data kecil (< 200 KB). Mencakup auth session token, user role ID, preferensi tema (light/dark), dan locale bahasa. State ini menentukan layout awal yang harus valid pada frame 0.
  • Deferred Non-Critical State (Async / SQLite / WatermelonDB): Data relasional bervolume besar, cache HTTP, daftar riwayat transaksi, atau draft form. Hydration untuk layer ini ditunda (deferred) setelah render awal selesai menggunakan promise atau query berbasis background.

Batasan dan Pertimbangan Teknis

  • Main Thread Blocking: Membaca string JSON berukuran megabyte secara synchronous di root mount via MMKV akan memblokir thread JavaScript utama dan native UI thread selama proses parsing. Batasi critical state hanya untuk key-value skalar atau objek kecil.
  • Enkripsi: MMKV mendukung enkripsi native AES-CFB-128. Gunakan fitur ini jika token autentikasi disimpan langsung di MMKV tanpa memakai OS Keystore/Keychain.
  • Platform Compatibility: MMKV bergantung penuh pada JSI. Pastikan toolchain mendukung C++17 dan versi React Native yang digunakan kompatibel dengan arsitektur JSI.