Hydration error pada Expo Router terjadi saat kompilasi Web SSR (Server-Side Rendering) atau SSG (Static Site Generation) menghasilkan pohon DOM (Document Object Model) yang berbeda dengan pohon render virtual yang dibangun React pada sisi klien saat pertama kali dimuat. React mewajibkan initial client render menghasilkan output identik dengan HTML yang dikirim oleh server.
Ketika terjadi ketidakcocokan atribut, struktur tag, atau nilai teks, browser menampilkan peringatan seperti Hydration failed because the initial UI does not match what was rendered on the server atau Text content did not match. Di Expo Router, isu ini lazim muncul karena aplikasi berjalan universal di Web dan Native sekaligus.
Akar Penyebab Hydration Mismatch di Expo Router
Penyebab utama dari hydration mismatch berakar pada evaluasi kode yang bergantung pada state atau environment runtime browser saat fase komputasi awal komponen. Di Expo Router, tiga pemicu paling umum meliputi:
- Akses Dimensi Perangkat: Penggunaan
Dimensions.get('window')atauuseWindowDimensionssecara sinkron. Pada fase SSR di Node.js, dimensi bernilai default (biasanya 0x0 atau ukuran desktop mock), sedangkan di browser klien nilai tersebut langsung merefleksikan viewport aktual. - Client Storage dan Browser Global: Membaca
localStorage, cookie, atau objekwindowsecara langsung saat mendeklarasikan initial state React. Server Node.js tidak memiliki akses ke penyimpanan klien ini. - Logika Platform Asimetris: Pengecekan
Platform.OS === 'web'yang merender markup berbeda yang bergantung pada kondisi runtime klien dinamis (misalnya deteksi preferensi warna via CSS vs JS) sebelum proses mounting tuntas.
Pola Masalah: Pembacaan Sinkron API Klien (Before)
Contoh di bawah menunjukkan implementasi komponen tata letak yang memecah render tree antara SSR server dan hydration klien:
import { View, Text, Dimensions } from 'react-native';
export default function ResponsiveHeader() {
// MASALAH: Nilai lebar di server SSR (Node.js) berbeda dengan di browser klien
const { width } = Dimensions.get('window');
const isMobile = width < 768;
return (
<View>
{isMobile ? (
<Text>Tampilan Mobile</Text>
) : (
<Text>Tampilan Desktop</Text>
)}
</View>
);
}Pada kode di atas, server me-render Tampilan Desktop ke dalam HTML statis. Namun saat browser membuka halaman, JavaScript membaca resolusi perangkat pengguna (misalnya ponsel dengan lebar 390px) dan mencoba me-render Tampilan Mobile pada pass pertama. React mendeteksi perbedaan teks dan memicu hydration error.
Solusi: Client Boundary Menggunakan Hook useHydrated
Pendekatan teknis yang benar adalah menunda evaluasi state spesifik klien hingga fase mounting selesai. useEffect hanya dijalankan pada sisi klien setelah proses hydration awal berhasil diselesaikan.
Alternatif paling ringkas: gunakan CSS media queries murni untuk styling responsif web. Jika logika komponen native tetap memerlukan percabangan JavaScript, buat custom hook useHydrated berikut:
import { useState, useEffect } from 'react';
export function useHydrated(): boolean {
const [hydrated, setHydrated] = useState(false);
useEffect(() => {
// ponytail: flag mount sederhana; tingkatkan ke useSyncExternalStore jika butuh sinkronisasi concurrent data
setHydrated(true);
}, []);
return hydrated;
}useHydrated() → skipped: context provider SSR dinamis, add when: state perlu disinkronkan langsung via request headers/cookie.
Implementasi Perbaikan (After)
Terapkan hook tersebut untuk menjaga integritas initial render tree antara server dan klien. Selama proses hydration berlangsung, tampilkan fallback atau representasi netral yang sama persis.
import { View, Text, useWindowDimensions } from 'react-native';
import { useHydrated } from '@/hooks/useHydrated';
export default function ResponsiveHeader() {
const isHydrated = useHydrated();
const { width } = useWindowDimensions();
// Render placeholder netral atau struktur server-safe hingga hydration selesai
if (!isHydrated) {
return (
<View>
<Text>Memuat Antarmuka...</Text>
</View>
);
}
const isMobile = width < 768;
return (
<View>
{isMobile ? (
<Text>Tampilan Mobile</Text>
) : (
<Text>Tampilan Desktop</Text>
)}
</View>
);
}Menangani Komponen Khusus Web vs Native
Jika memiliki komponen yang sepenuhnya bergantung pada API web DOM pihak ketiga, gunakan ekstensi file spesifik platform untuk memisahkan implementasi daripada melakukan branching di dalam satu file komponen:
Widget.web.tsx: Menangani perilaku spesifik web dengan guard hydration.Widget.tsx: Menangani implementasi native (iOS & Android) langsung tanpa overhead hydration web.
Expo Router otomatis memilih file yang relevan saat proses bundling. Ini mengeliminasi dead code dan mencegah modul klien web dievaluasi di runtime native atau sebaliknya.
Langkah Verifikasi
- Verifikasi Web Development: Jalankan
npx expo start --web. Buka DevTools konsol browser. Pastikan tidak ada warning bertandareact-domterkait hydration mismatch saat memuat ulang halaman (Hard Refresh: Ctrl+F5 / Cmd+Shift+R). - Verifikasi Static Export / Production Web: Jalankan
npx expo export -p web. Jalankan server lokal dari folder build statis (misal:npx serve dist). Akses URL untuk menguji perilaku HTML statis tanpa hot reloading. - Verifikasi Native: Jalankan
npx expo start --androidataunpx expo start --ios. Pastikan aplikasi native tidak menampilkan flicker visual berlebih akibat penundaan mount, karenauseEffectlangsung dieksekusi pada lingkungan native tanpa fase SSR HTML.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!