REACT FRONTEND JAVASCRIPT WEB-PERFORMANCE USER-EXPERIENCE MODERN-WEB CONCURRENT-REACT USETRANSITION USEDEFERREDVALUE SUSPENSE UI-UX OPTIMIZATION RENDERING

Membangun UI Responsif dengan Concurrent React: Menguasai useTransition, useDeferredValue, dan Suspense

⏱️ 13 menit baca
👨‍💻

Membangun UI Responsif dengan Concurrent React: Menguasai useTransition, useDeferredValue, dan Suspense

Pernahkah Anda merasa frustrasi saat aplikasi web yang Anda bangun terasa lambat atau “freeze” sebentar ketika ada banyak data yang diproses atau saat navigasi antar halaman? Rasanya seperti UI tiba-tiba macet, tidak responsif terhadap input pengguna, dan pengalaman pengguna pun jadi terganggu. Ini adalah masalah umum dalam pengembangan web, terutama dengan aplikasi yang kaya fitur dan data.

Sebagai developer, kita selalu mencari cara untuk membuat aplikasi lebih cepat dan lebih responsif. Di sinilah Concurrent React hadir sebagai game-changer. Concurrent React adalah fitur fundamental dari React yang memungkinkan aplikasi Anda tetap responsif bahkan saat melakukan tugas-tugas berat di latar belakang. Ini bukan tentang membuat kode Anda berjalan lebih cepat secara absolut, melainkan tentang bagaimana React dapat mengelola prioritas dan menunda pekerjaan yang tidak mendesak agar UI tetap interaktif.

Dalam artikel ini, kita akan menyelami dunia Concurrent React dan tiga pilar utamanya yang akan membantu Anda membangun UI yang jauh lebih mulus: useTransition, useDeferredValue, dan Suspense. Mari kita mulai!

1. Memahami Masalah: Blocking Renders di React Tradisional

Secara default, React bekerja secara sinkron. Artinya, ketika Anda mengupdate state, React akan segera mencoba merender ulang seluruh komponen yang terpengaruh. Jika update state tersebut memicu banyak perhitungan atau rendering komponen yang kompleks, seluruh thread utama browser bisa terblokir.

Skenario Umum:

📌 Masalah utamanya adalah React tidak bisa “menginterupsi” pekerjaan yang sedang berjalan untuk merespons input pengguna yang lebih penting.

2. Apa Itu Concurrent React?

Concurrent React adalah arsitektur baru di React yang memungkinkan React untuk menghentikan, menunda, atau melanjutkan pekerjaan rendering berdasarkan prioritas. Ini seperti memiliki “otak” tambahan yang bisa memutuskan pekerjaan mana yang lebih penting untuk dilakukan sekarang, dan mana yang bisa menunggu.

Konsep Kunci:

Manfaatnya: UI aplikasi Anda akan terasa lebih responsif, cepat, dan memberikan pengalaman pengguna yang lebih baik, bahkan pada perangkat yang kurang bertenaga atau dengan data yang kompleks.

3. useTransition: Menjaga UI Tetap Responsif Selama Transisi

useTransition adalah hook yang memungkinkan Anda menandai pembaruan state sebagai “transisi” (non-urgent). Ini memberitahu React bahwa update tersebut bisa ditunda jika ada pekerjaan yang lebih mendesak.

🎯 Kapan Menggunakan useTransition? Ketika Anda memiliki update state yang memicu perubahan UI yang signifikan atau perhitungan yang berat, tetapi Anda tidak ingin update tersebut memblokir interaksi pengguna lainnya. Contoh paling umum adalah filtering data, navigasi, atau sorting.

Bagaimana Cara Kerjanya?

useTransition mengembalikan array dengan dua elemen:

  1. isPending: Sebuah boolean yang menunjukkan apakah transisi sedang berlangsung. Berguna untuk menampilkan indikator loading.
  2. startTransition: Sebuah fungsi yang Anda gunakan untuk membungkus update state yang ingin Anda tandai sebagai transisi.

Contoh Konkret: Input Pencarian yang Responsif

Misalkan Anda memiliki input pencarian yang memfilter daftar panjang. Tanpa useTransition, setiap kali Anda mengetik, UI mungkin terasa lambat.

import React, { useState, useTransition } from 'react';

const generateBigList = (size) => {
  const list = [];
  for (let i = 0; i < size; i++) {
    list.push(`Item ${i + 1}`);
  }
  return list;
};

const items = generateBigList(10000); // Daftar yang sangat panjang

function SearchableList() {
  const [inputValue, setInputValue] = useState('');
  const [searchQuery, setSearchQuery] = useState('');
  const [isPending, startTransition] = useTransition(); // 👈 useTransition di sini

  const handleChange = (e) => {
    setInputValue(e.target.value); // Ini adalah urgent update (langsung responsif)

    // Bungkus update searchQuery dengan startTransition
    startTransition(() => {
      setSearchQuery(e.target.value); // Ini adalah non-urgent update
    });
  };

  const filteredItems = items.filter(item =>
    item.toLowerCase().includes(searchQuery.toLowerCase())
  );

  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={handleChange}
        placeholder="Cari item..."
        style={{ width: '300px', padding: '10px', fontSize: '16px' }}
      />
      {isPending && <p style={{ color: 'gray' }}>Sedang mencari...</p>} {/* 👈 Indikator loading */}
      <ul style={{ maxHeight: '300px', overflowY: 'auto', border: '1px solid #eee', marginTop: '10px' }}>
        {filteredItems.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default SearchableList;

Dalam contoh ini:

4. useDeferredValue: Menunda Update UI yang Berat

useDeferredValue mirip dengan useTransition, tetapi digunakan untuk menunda rendering nilai di dalam komponen, bukan membungkus update state. Ini sangat berguna ketika Anda memiliki nilai yang membutuhkan waktu untuk dirender, dan Anda ingin React menunda rendering nilai tersebut sampai thread utama browser bebas.

🎯 Kapan Menggunakan useDeferredValue? Ketika Anda ingin menunjukkan versi “lama” dari UI sementara versi “baru” yang berat sedang dihitung di latar belakang. Ini seperti debounce yang dioptimalkan oleh React scheduler.

Bagaimana Cara Kerjanya?

useDeferredValue(value) akan mengembalikan versi “tertunda” dari value. React akan mencoba memperbarui nilai tertunda ini ketika ada waktu luang, tanpa memblokir UI.

Contoh Konkret: Menampilkan Hasil Pencarian yang Difilter (lanjutan dari useTransition)

Mari kita gunakan kembali contoh SearchableList dan terapkan useDeferredValue untuk daftar hasil.

import React, { useState, useTransition, useDeferredValue } from 'react';

const generateBigList = (size) => {
  const list = [];
  for (let i = 0; i < size; i++) {
    list.push(`Item ${i + 1}`);
  }
  return list;
};

const items = generateBigList(10000);

function SearchableListDeferred() {
  const [inputValue, setInputValue] = useState('');
  const [isPending, startTransition] = useTransition();
  const deferredInputValue = useDeferredValue(inputValue); // 👈 Nilai input yang ditunda

  // Menandai apakah deferredInputValue masih "lama" atau sedang menunggu update
  const isDeferredPending = inputValue !== deferredInputValue;

  const handleChange = (e) => {
    setInputValue(e.target.value);
  };

  // Gunakan deferredInputValue untuk filter, bukan langsung inputValue
  const filteredItems = items.filter(item =>
    item.toLowerCase().includes(deferredInputValue.toLowerCase())
  );

  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={handleChange}
        placeholder="Cari item..."
        style={{ width: '300px', padding: '10px', fontSize: '16px' }}
      />
      {/* Indikator loading untuk deferred value */}
      {isDeferredPending && <p style={{ color: 'gray' }}>Sedang memperbarui hasil...</p>}
      <ul style={{ maxHeight: '300px', overflowY: 'auto', border: '1px solid #eee', marginTop: '10px' }}>
        {filteredItems.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default SearchableListDeferred;

Perbedaan utama dengan useTransition:

Dalam contoh ini, inputValue akan segera diupdate, tetapi deferredInputValue akan menunggu sampai React memiliki waktu luang untuk memperbarui hasil filter. Ini memastikan input field tetap responsif, dan daftar hasil akan diperbarui secara mulus di latar belakang.

5. Suspense: Menangani Data Fetching dan Code Splitting dengan Elegan

Suspense adalah fitur React yang memungkinkan komponen “menunggu” sesuatu (seperti data, kode, atau gambar) sebelum dirender. Ini memberikan cara yang deklaratif untuk mengelola status loading, bukan dengan isLoading boolean yang seringkali membuat kode menjadi berantakan.

🎯 Kapan Menggunakan Suspense? Untuk menampilkan UI fallback (misalnya, spinner loading) saat komponen atau data yang dibutuhkan belum siap. Paling sering digunakan untuk code splitting (React.lazy) dan data fetching.

Contoh Konkret: Code Splitting dengan React.lazy

Ini adalah penggunaan Suspense yang paling umum dan sudah ada sejak lama.

import React, { Suspense } from 'react';

// Lazy load komponen DetailProduk
const DetailProduk = React.lazy(() => import('./DetailProduk'));

function App() {
  const [showDetails, setShowDetails] = useState(false);

  return (
    <div>
      <h1>Aplikasi Toko Online</h1>
      <button onClick={() => setShowDetails(!showDetails)}>
        {showDetails ? 'Sembunyikan' : 'Tampilkan'} Detail Produk
      </button>

      {showDetails && (
        <Suspense fallback={<div>Loading Detail Produk...</div>}> {/* 👈 Suspense */}
          <DetailProduk />
        </Suspense>
      )}
    </div>
  );
}

// Komponen DetailProduk.js (akan di-lazy load)
// function DetailProduk() {
//   return (
//     <div>
//       <h2>Detail Produk Spesial</h2>
//       <p>Ini adalah deskripsi produk yang dimuat secara dinamis.</p>
//     </div>
//   );
// }

Ketika showDetails menjadi true, React akan mencoba memuat komponen DetailProduk. Selama proses pemuatan, fallback dari Suspense akan ditampilkan. Setelah DetailProduk selesai dimuat, ia akan menggantikan fallback.

Suspense untuk Data Fetching (dengan Library)

Meskipun React belum menyediakan solusi built-in untuk data fetching dengan Suspense secara langsung (Anda tidak bisa hanya await Promise di komponen), library seperti React Query (TanStack Query) atau SWR telah mengintegrasikan dukungan Suspense.

Konsepnya:

  1. Anda mengkonfigurasi Suspense di atas komponen yang melakukan fetching data.
  2. Komponen tersebut akan “melempar” promise saat data belum siap.
  3. Suspense akan menangkap promise tersebut dan menampilkan fallback UI.
  4. Setelah promise selesai (data fetched), komponen akan dirender dengan data.
// Contoh pseudo-code dengan React Query (membutuhkan konfigurasi Suspense di QueryClientProvider)
import React, { Suspense } from 'react';
import { useQuery } from '@tanstack/react-query';

function fetchUserData() {
  return new Promise(resolve => setTimeout(() => resolve({ name: 'Budi', email: 'budi@example.com' }), 2000));
}

function UserProfile() {
  // useQuery akan "melempar" promise jika data belum ada dan Suspense diaktifkan
  const { data: user } = useQuery({ queryKey: ['user'], queryFn: fetchUserData, suspense: true });

  return (
    <div>
      <h2>Profil Pengguna</h2>
      <p>Nama: {user.name}</p>
      <p>Email: {user.email}</p>
    </div>
  );
}

function AppWithSuspense() {
  return (
    <Suspense fallback={<div>Memuat profil pengguna...</div>}>
      <UserProfile />
    </Suspense>
  );
}

export default AppWithSuspense;

⚠️ Penting: Suspense untuk data fetching membutuhkan setup yang benar di sisi library data fetching Anda dan juga bagaimana Anda mengelola Error Boundaries untuk menangani kegagalan fetching data.

6. Best Practices dan Kapan Menggunakan Masing-Masing

Memahami kapan dan bagaimana menggunakan useTransition, useDeferredValue, dan Suspense adalah kunci untuk membangun aplikasi yang optimal.

FiturKegunaan UtamaKapan Digunakan
useTransitionMenandai update state sebagai non-urgent, menjaga UI tetap responsif.Untuk update state yang memicu rendering berat (filtering, sorting, navigasi) di mana Anda ingin input pengguna tetap direspons.
useDeferredValueMenunda rendering nilai mahal di dalam komponen, menunjukkan versi lama sementara.Ketika Anda memiliki nilai yang membutuhkan waktu untuk dirender, dan Anda ingin menampilkan versi lama sampai versi baru siap.
SuspenseMenampilkan UI fallback saat komponen atau data belum siap.Untuk code splitting (React.lazy) atau data fetching (dengan library yang mendukung Suspense).

Tips Tambahan:

Kesimpulan

Concurrent React, dengan useTransition, useDeferredValue, dan Suspense, adalah langkah maju yang signifikan dalam cara kita membangun aplikasi web. Ini memungkinkan kita untuk merancang UI yang secara inheren lebih responsif dan mulus, bahkan ketika berhadapan dengan data besar atau operasi rendering yang kompleks.

Dengan menguasai alat-alat ini, Anda tidak hanya meningkatkan performa teknis aplikasi Anda, tetapi juga secara drastis meningkatkan pengalaman pengguna. Jadi, lain kali Anda menghadapi masalah UI yang lambat atau tidak responsif, ingatlah Concurrent React dan berikan aplikasi Anda “superpower” yang layak didapatkan!

🔗 Baca Juga