useEffect adalah Hook React yang memungkinkan Anda menyinkronkan komponen dengan sistem eksternal.

useEffect(setup, dependencies?)

Referensi

useEffect(setup, dependencies?)

Panggil useEffect di level atas komponen Anda untuk mendeklarasikan sebuah Effect:

import { useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}

Lihat lebih banyak contoh di bawah ini.

Parameter

  • setup: Fungsi dengan logika Effect Anda. Fungsi setup Anda juga dapat secara opsional mengembalikan fungsi cleanup. Ketika komponen Anda pertama kali ditambahkan ke DOM, React akan menjalankan fungsi setup Anda. Setelah setiap re-render dengan dependensi yang berubah, React akan pertama-tama menjalankan fungsi cleanup (jika Anda menyediakannya) dengan nilai lama, dan kemudian menjalankan fungsi setup Anda dengan nilai baru. Setelah komponen Anda dihapus dari DOM, React akan menjalankan fungsi cleanup Anda untuk terakhir kalinya.

  • dependensi opsional: Daftar semua nilai reaktif yang direferensikan di dalam kode setup. Nilai reaktif meliputi props, state, dan semua variabel dan fungsi yang dideklarasikan langsung di dalam body komponen Anda. Jika linter Anda dikonfigurasi untuk React, itu akan memverifikasi bahwa setiap nilai reaktif dijelaskan dengan benar sebagai dependensi. Daftar dependensi harus memiliki jumlah item yang konstan dan ditulis secara inline seperti [dep1, dep2, dep3]. React akan membandingkan setiap dependensi dengan nilai sebelumnya menggunakan perbandingan Object.is . Jika Anda mengabaikan argumen ini, Effect Anda akan berjalan ulang setelah setiap re-render dari komponen. Lihat perbedaan antara melewatkan array dependensi, array kosong, dan tidak ada dependensi sama sekali.

Kembalian

useEffect mengembalikan undefined.

Catatan Penting

  • useEffect adalah Hook, sehingga Anda hanya dapat memanggilnya di level atas komponen Anda atau Hook Anda sendiri. Anda tidak dapat memanggilnya di dalam loop atau kondisi. Jika Anda membutuhkannya, ekstraklah komponen baru dan pindahkan state ke dalamnya.

  • Jika Anda tidak mencoba untuk menyinkronkan dengan sistem eksternal tertentu, kemungkinan Anda tidak memerlukan sebuah Effect.

  • Ketika Strict Mode diaktifkan, React akan menjalankan satu siklus setup+cleanup tambahan hanya untuk pengembangan sebelum setup sebenarnya yang pertama. Ini adalah stress-test yang memastikan bahwa logika cleanup Anda “mencerminkan” logika setup Anda dan menghentikan atau membatalkan apa pun yang dilakukan setup. Jika ini menyebabkan masalah, implementasikan fungsi cleanup.

  • Jika beberapa dependensi Anda adalah objek atau fungsi yang didefinisikan di dalam komponen, ada risiko bahwa mereka akan membuat Effect berjalan ulang lebih sering dari yang diperlukan. Untuk mengatasinya, hapus dependensi objek dan fungsi yang tidak diperlukan. Anda juga dapat mengekstrak pembaruan state dan logika non-reaktif di luar Effect Anda.

  • Jika Effect Anda tidak disebabkan oleh interaksi (seperti klik), React akan membiarkan browser menampilkan layar yang diperbarui terlebih dahulu sebelum menjalankan Effect Anda. Jika Effect Anda melakukan sesuatu yang visual (misalnya, menempatkan tooltip), dan penundaannya terasa (misalnya, berkedip), gantilah useEffect dengan useLayoutEffect.

  • Meskipun Effect Anda disebabkan oleh interaksi (seperti klik), browser mungkin akan memperbarui tampilan layar sebelum memproses pembaruan state di dalam Efek Anda. Biasanya, itu adalah yang Anda inginkan. Namun, jika Anda harus mencegah browser memperbarui tampilan layar, Anda perlu mengganti useEffect dengan useLayoutEffect.

  • Effects hanya berjalan di sisi klien. Mereka tidak berjalan selama server rendering.


Penggunaan

Menghubungkan ke sistem eksternal

Beberapa komponen perlu terhubung dengan jaringan, beberapa API browser, atau perpustakaan pihak ketiga, saat ditampilkan pada halaman. Sistem-sistem ini tidak dikendalikan oleh React, sehingga disebut sebagai sistem eksternal.

Untuk menghubungkan komponen Anda ke sistem eksternal, panggil useEffect pada level atas komponen Anda:

import { useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}

Anda perlu melewatkan dua argumen ke useEffect:

  1. Sebuah fungsi setup dengan kode setup yang menghubungkan ke sistem tersebut.
    • Fungsi tersebut harus mengembalikan sebuah fungsi cleanup dengan kode cleanup yang memutus koneksi dari sistem tersebut.
  2. Sebuah daftar dependensi termasuk setiap nilai dari komponen Anda yang digunakan di dalam fungsi-fungsi tersebut.

React memanggil fungsi setup dan cleanup Anda kapan saja diperlukan, yang mungkin terjadi beberapa kali:

  1. Kode setup Anda dijalankan ketika komponen Anda ditambahkan ke halaman (mounts).
  2. Setelah setiap re-render dari komponen Anda di mana dependensi telah berubah:
    • Pertama, kode cleanup Anda dijalankan dengan props dan state yang lama.
    • Kemudian, kode setup Anda dijalankan dengan props dan state yang baru.
  3. Kode cleanup Anda dijalankan satu kali terakhir setelah komponen Anda dihapus dari halaman (unmounts).

Mari ilustrasikan urutan ini untuk contoh di atas.

Ketika komponen ChatRoom di atas ditambahkan ke halaman, itu akan terhubung ke ruang obrolan dengan serverUrl dan roomId awal. Jika salah satu dari serverUrl atau roomId berubah sebagai hasil dari re-render (misalnya, jika pengguna memilih ruang obrolan yang berbeda dalam dropdown), Effect Anda akan memutuskan koneksi dari ruang sebelumnya, dan terhubung ke yang berikutnya. Ketika komponen ChatRoom dihapus dari halaman, Effect Anda akan memutuskan koneksi satu kali terakhir.

Untuk membantu Anda menemukan bug, dalam pengembangan React menjalankan setup dan cleanup satu kali ekstra sebelum setup. Ini adalah pengujian stress-test yang memverifikasi logika Effect Anda diimplementasikan dengan benar. Jika ini menyebabkan masalah yang terlihat, fungsi cleanup Anda kekurangan beberapa logika. Fungsi cleanup harus menghentikan atau membatalkan apa yang dilakukan oleh fungsi setup. Aturan praktisnya adalah bahwa pengguna tidak boleh dapat membedakan antara setup yang dipanggil sekali (seperti di produksi) dan urutan setupcleanupsetup (seperti di pengembangan). Lihat solusi umum.

Cobalah untuk menulis setiap Effect sebagai proses independen dan berpikir tentang satu siklus setup/cleanup pada suatu waktu. Tidak harus masalah apakah komponen Anda sedang mounting, updating, atau unmounting. Ketika logika cleanup Anda “mencerminkan” logika setup dengan benar, Effect Anda tangguh terhadap menjalankan setup dan cleanup sesering yang diperlukan.

Catatan

Sebuah Effect memungkinkan Anda menjaga sinkronisasi komponen Anda dengan beberapa sistem eksternal (seperti layanan obrolan). Di sini, sistem eksternal berarti setiap kode yang tidak dikontrol oleh React, seperti:

Jika Anda tidak terhubung ke sistem eksternal apa pun, kemungkinan Anda tidak memerlukan Effect.

Contoh-contoh menghubungkan ke sistem eksternal

Contoh 1 dari 5:
Menghubungkan ke server obrolan

Pada contoh ini, komponen ChatRoom menggunakan sebuah Effect untuk tetap terhubung ke sistem eksternal yang didefinisikan dalam chat.js. Tekan “Buka obrolan” untuk membuat komponen ChatRoom muncul. Sandbox ini berjalan dalam mode pengembangan, sehingga terdapat siklus koneksi-dan-putus tambahan, seperti yang dijelaskan di sini. Cobalah mengubah roomId dan serverUrl menggunakan dropdown dan input, dan lihat bagaimana Effect terhubung kembali ke obrolan. Tekan “Tutup obrolan” untuk melihat Effect memutuskan koneksi untuk terakhir kalinya.

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [roomId, serverUrl]);

  return (
    <>
      <label>
        Server URL:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Welcome to the {roomId} room!</h1>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Close chat' : 'Open chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}


Membungkus Effect dalam Hook kustom

Effects adalah “pintu darurat”: Anda menggunakannya ketika Anda perlu “keluar dari React” dan ketika tidak ada solusi bawaan yang lebih baik untuk kasus penggunaan Anda. Jika Anda sering perlu menulis Effects secara manual, itu biasanya menjadi tanda bahwa Anda perlu mengekstrak beberapa custom Hooks untuk perilaku umum yang dibutuhkan komponen Anda.

Misalnya, custom Hook useChatRoom ini “menyembunyikan” logika Effect Anda di balik API yang lebih deklaratif:

function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]);
}

Lalu kamu bisa menggunakannya dari komponen mana pun seperti ini:

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useChatRoom({
roomId: roomId,
serverUrl: serverUrl
});
// ...

Ada juga banyak custom Hooks yang sangat bagus untuk setiap tujuan yang tersedia di ekosistem React.

Pelajari lebih lanjut tentang cara mengelompokkan Effects ke dalam Custom Hooks.

Contoh-contoh *Effect* pembungkusan di *custom Hooks*

Contoh 1 dari 3:
Custom useChatRoom Hook

Contoh ini sama dengan salah satu contoh sebelumnya, tetapi logikanya diekstraksi ke dalam custom Hook.

import { useState } from 'react';
import { useChatRoom } from './useChatRoom.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useChatRoom({
    roomId: roomId,
    serverUrl: serverUrl
  });

  return (
    <>
      <label>
        Server URL:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Welcome to the {roomId} room!</h1>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Close chat' : 'Open chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}


Mengontrol widget non-React

Terkadang, Anda ingin menjaga sistem eksternal tetap disinkronkan dengan beberapa prop atau state dari komponen React Anda.

Misalnya, jika Anda memiliki widget peta pihak ketiga atau komponen pemutar video yang ditulis tanpa React, Anda dapat menggunakan Effect untuk memanggil metode pada widget tersebut yang membuat state sesuai dengan state saat ini dari komponen React Anda. Effect ini menciptakan sebuah instance dari kelas MapWidget yang didefinisikan dalam map-widget.js. Ketika Anda mengubah prop zoomLevel dari komponen Map, Effect memanggil setZoom() pada instance kelas untuk menjaganya tetap disinkronkan:

import { useRef, useEffect } from 'react';
import { MapWidget } from './map-widget.js';

export default function Map({ zoomLevel }) {
  const containerRef = useRef(null);
  const mapRef = useRef(null);

  useEffect(() => {
    if (mapRef.current === null) {
      mapRef.current = new MapWidget(containerRef.current);
    }

    const map = mapRef.current;
    map.setZoom(zoomLevel);
  }, [zoomLevel]);

  return (
    <div
      style={{ width: 200, height: 200 }}
      ref={containerRef}
    />
  );
}

Pada contoh ini, sebuah fungsi cleanup tidak diperlukan karena kelas MapWidget hanya mengelola node DOM yang diberikan kepadanya. Setelah komponen Map React dihapus dari pohon(tree), baik node DOM maupun instance kelas MapWidget akan otomatis dihapus oleh mesin JavaScript pada browser.


Mengambil data dengan Effects

Anda dapat menggunakan sebuah Effect untuk mengambil data untuk komponen Anda. Perlu diingat bahwa jika Anda menggunakan sebuah framework, menggunakan mekanisme pengambilan data dari framework Anda akan jauh lebih efisien daripada menulis Effects secara manual.

Jika Anda ingin mengambil data dari sebuah Effect secara manual, kode Anda mungkin akan terlihat seperti ini:

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
const [person, setPerson] = useState('Alice');
const [bio, setBio] = useState(null);

useEffect(() => {
let ignore = false;
setBio(null);
fetchBio(person).then(result => {
if (!ignore) {
setBio(result);
}
});
return () => {
ignore = true;
};
}, [person]);

// ...

Perhatikan variabel ignore yang diinisialisasi dengan nilai false dan diatur menjadi true selama cleanup. Ini memastikan kode Anda tidak mengalami “race conditions”: respon jaringan dapat tiba dalam urutan yang berbeda dari yang Anda kirimkan.

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);
  useEffect(() => {
    let ignore = false;
    setBio(null);
    fetchBio(person).then(result => {
      if (!ignore) {
        setBio(result);
      }
    });
    return () => {
      ignore = true;
    }
  }, [person]);

  return (
    <>
      <select value={person} onChange={e => {
        setPerson(e.target.value);
      }}>
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Taylor">Taylor</option>
      </select>
      <hr />
      <p><i>{bio ?? 'Loading...'}</i></p>
    </>
  );
}

Anda juga dapat menulis kode dengan menggunakan sintaksis async / await, namun Anda masih perlu menyediakan fungsi cleanup:

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);
  useEffect(() => {
    async function startFetching() {
      setBio(null);
      const result = await fetchBio(person);
      if (!ignore) {
        setBio(result);
      }
    }

    let ignore = false;
    startFetching();
    return () => {
      ignore = true;
    }
  }, [person]);

  return (
    <>
      <select value={person} onChange={e => {
        setPerson(e.target.value);
      }}>
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Taylor">Taylor</option>
      </select>
      <hr />
      <p><i>{bio ?? 'Loading...'}</i></p>
    </>
  );
}

Menulis pengambilan data langsung di Effects menjadi repetitif dan sulit untuk menambahkan optimasi seperti caching dan server rendering nanti. Lebih mudah untuk menggunakan Custom Hook - baik yang Anda buat sendiri atau yang dipelihara oleh komunitas.

Pendalaman

Apa alternatif yang bagus untuk pengambilan data di Effects?

Menulis panggilan fetch di dalam Effects adalah cara yang populer untuk mengambil data, terutama dalam aplikasi yang sepenuhnya berbasis klien. Namun, ini adalah pendekatan yang sangat manual dan memiliki beberapa kekurangan:

  • Effects tidak berjalan di server. Ini berarti HTML yang dirender oleh server hanya akan berisi state loading tanpa data. Komputer klien harus mengunduh semua JavaScript dan merender aplikasi Anda hanya untuk menemukan bahwa sekarang ia perlu memuat data. Ini tidak efisien.
  • Mengambil data secara langsung dalam Effects membuatnya mudah untuk membuat “waterfalls jaringan”. Anda merender komponen induk(parent), ia mengambil beberapa data, merender komponen anak(child), dan kemudian mereka mulai mengambil data mereka. Jika jaringannya tidak terlalu cepat, ini jauh lebih lambat daripada mengambil semua data secara paralel.
  • Mengambil data secara langsung dalam Effects biasanya berarti Anda tidak memuat atau menyimpan data di cache. Misalnya, jika komponen di-unmount dan kemudian di-mount lagi, maka ia harus mengambil data lagi.
  • Tidak ergonomis. Ada cukup banyak kode boilerplate yang terlibat saat menulis panggilan fetch dengan cara yang tidak menderita dari bug seperti race condition.

Daftar kekurangan ini tidak spesifik untuk React. Ini berlaku untuk mengambil data saat mount dengan library manapun. Seperti dengan routing, pengambilan data tidak mudah dilakukan dengan baik, jadi kami sarankan pendekatan berikut:

  • Jika Anda menggunakan framework, gunakan mekanisme pengambilan data bawaannya. Framework React modern memiliki mekanisme pengambilan data terintegrasi yang efisien dan tidak menderita dari masalah di atas.
  • Jika tidak, pertimbangkan untuk menggunakan atau membangun cache sisi klien. Solusi open source populer termasuk React Query, useSWR, dan React Router 6.4+. Anda juga dapat membangun solusi Anda sendiri, dalam hal ini Anda akan menggunakan Effects di bawah kap, tetapi juga menambahkan logika untuk mendeduplikasi permintaan, caching respons, dan menghindari air terjun(waterfalls) jaringan (dengan memuat data atau mengangkat persyaratan data ke route).

Anda dapat terus mengambil data secara langsung dalam Effects jika kedua pendekatan ini tidak cocok untuk Anda.


Menentukan dependensi yang responsif

Perhatikan bahwa Anda tidak dapat “memilih” dependensi dari Effect Anda. Setiap nilai reaktif yang digunakan oleh kode Effect Anda harus dideklarasikan sebagai dependensi. Daftar dependensi Effect Anda ditentukan oleh kode sekitarnya:

function ChatRoom({ roomId }) { // This is a reactive value
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // This is a reactive value too

useEffect(() => {
const connection = createConnection(serverUrl, roomId); // This Effect reads these reactive values
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]); // ✅ So you must specify them as dependencies of your Effect
// ...
}

Jika serverUrl atau roomId berubah, Effect Anda akan menyambung kembali ke obrolan menggunakan nilai baru.

Nilai reaktif mencakup props dan semua variabel dan fungsi yang dideklarasikan langsung di dalam komponen Anda. Karena roomId dan serverUrl adalah nilai reaktif, Anda tidak dapat menghapusnya dari dependensi. Jika Anda mencoba menghilangkannya dan linter Anda dikonfigurasi dengan benar untuk React, linter akan menandai ini sebagai kesalahan yang perlu Anda perbaiki:

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // 🔴 React Hook useEffect has missing dependencies: 'roomId' and 'serverUrl'
// ...
}

Untuk menghapus sebuah dependensi, Anda perlu “membuktikan” pada linter bahwa itu tidak perlu menjadi sebuah dependensi. Misalnya, Anda dapat memindahkan serverUrl keluar dari komponen Anda untuk membuktikan bahwa itu tidak reaktif dan tidak akan berubah pada saat re-render:

const serverUrl = 'https://localhost:1234'; // Not a reactive value anymore

function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ All dependencies declared
// ...
}

Sekarang serverUrl bukan lagi sebuah nilai yang reaktif (dan tidak bisa berubah pada re-render), sehingga tidak perlu menjadi dependensi. Jika kode Effect Anda tidak menggunakan nilai yang reaktif, daftar dependensinya harus kosong ([]):

const serverUrl = 'https://localhost:1234'; // Not a reactive value anymore
const roomId = 'music'; // Not a reactive value anymore

function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ All dependencies declared
// ...
}

Sebuah Effect dengan daftar dependencies kosong tidak akan dijalankan ulang ketika props atau state dari komponen berubah.

Sandungan

Jika Anda memiliki basis kode yang sudah ada, mungkin Anda memiliki beberapa Effect yang menekan linter seperti ini:

useEffect(() => {
// ...
// 🔴 Avoid suppressing the linter like this:
// eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);

Ketika dependensi tidak cocok dengan kode, risiko mengenalkan bug cukup tinggi. Dengan menekan linter, Anda “berbohong” pada React tentang nilai-nilai yang dibutuhkan oleh Effect Anda. Sebaliknya, buktikan bahwa dependensi tersebut tidak diperlukan.

Examples of passing reactive dependencies

Contoh 1 dari 3:
Mengoper sebuah array dependensi

Jika kamu menentukan dependensi, Effect kamu akan dijalankan setelah render pertama dan setelah re-render dengan dependensi yang berubah.

useEffect(() => {
// ...
}, [a, b]); // Runs again if a or b are different

Dalam contoh di bawah ini, serverUrl dan roomId adalah nilai reaktif, sehingga keduanya harus disebutkan sebagai dependensi. Sebagai hasilnya, memilih ruangan yang berbeda dalam dropdown atau mengedit input URL server menyebabkan obrolan terhubung kembali. Namun, karena pesan tidak digunakan dalam Effect (dan karena itu bukan dependensi), mengedit pesan tidak menyebabkan obrolan terhubung kembali.

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');
  const [message, setMessage] = useState('');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [serverUrl, roomId]);

  return (
    <>
      <label>
        Server URL:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Welcome to the {roomId} room!</h1>
      <label>
        Your message:{' '}
        <input value={message} onChange={e => setMessage(e.target.value)} />
      </label>
    </>
  );
}

export default function App() {
  const [show, setShow] = useState(false);
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
        <button onClick={() => setShow(!show)}>
          {show ? 'Close chat' : 'Open chat'}
        </button>
      </label>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId}/>}
    </>
  );
}


Memperbarui state berdasarkan state sebelumnya dari sebuah Effect

Ketika Anda ingin memperbarui state berdasarkan state sebelumnya dari sebuah Effect, Anda mungkin akan menghadapi masalah:

function Counter() {
const [count, setCount] = useState(0);

useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1); // You want to increment the counter every second...
}, 1000)
return () => clearInterval(intervalId);
}, [count]); // 🚩 ... but specifying `count` as a dependency always resets the interval.
// ...
}

Karena count adalah nilai reaktif, ia harus disebutkan dalam daftar dependensi. Namun, hal ini menyebabkan Effect cleanup dan mengatur ulang setiap kali count berubah. Ini tidak ideal.

Untuk mengatasinya, oper pembaruan status c => c + 1 ke setCount:

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(c => c + 1); // ✅ Pass a state updater
    }, 1000);
    return () => clearInterval(intervalId);
  }, []); // ✅ Now count is not a dependency

  return <h1>{count}</h1>;
}

Sekarang bahwa Anda mengoper c => c + 1 bukan count + 1, Effect Anda tidak lagi perlu tergantung pada count. Sebagai hasil dari perbaikan ini, ia tidak perlu cleanup dan mengatur interval lagi setiap kali count berubah.


Menghapus dependensi objek yang tidak diperlukan

Jika Effect Anda bergantung pada objek atau fungsi yang dibuat selama rendering, mungkin berjalan terlalu sering. Misalnya, Effect ini terhubung kembali setelah setiap render karena objek opsi berbeda untuk setiap render:

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

const options = { // 🚩 This object is created from scratch on every re-render
serverUrl: serverUrl,
roomId: roomId
};

useEffect(() => {
const connection = createConnection(options); // It's used inside the Effect
connection.connect();
return () => connection.disconnect();
}, [options]); // 🚩 As a result, these dependencies are always different on a re-render
// ...

Hindari menggunakan objek yang dibuat selama rendering sebagai dependensi. Sebaliknya, buat objek di dalam Effect:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>Welcome to the {roomId} room!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Sekarang setelah Anda membuat objek opsi di dalam Effect, Effect itu sendiri hanya tergantung pada string roomId.

Dengan perbaikan ini, mengetik ke dalam input tidak akan menyambungkan kembali ke chat. Berbeda dengan objek yang dibuat ulang, string seperti roomId tidak berubah kecuali Anda menetapkannya ke nilai lain. Baca lebih lanjut tentang menghapus dependensi.


Menghapus dependensi fungsi yang tidak perlu

Jika Effect Anda bergantung pada objek atau fungsi yang dibuat selama rendering, maka Effect tersebut mungkin akan berjalan terlalu sering. Misalnya, Effect ini akan terhubung kembali setelah setiap rendering karena fungsi createOptions berbeda untuk setiap rendering:

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

function createOptions() { // 🚩 This function is created from scratch on every re-render
return {
serverUrl: serverUrl,
roomId: roomId
};
}

useEffect(() => {
const options = createOptions(); // It's used inside the Effect
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🚩 As a result, these dependencies are always different on a re-render
// ...

Jika hanya membuat sebuah fungsi dari awal pada setiap re-render, itu bukan masalah yang perlu dioptimalkan. Namun, jika Anda menggunakannya sebagai dependensi dari Effect Anda, maka akan menyebabkan Effect Anda berjalan kembali setelah setiap re-render.

Hindari menggunakan sebuah fungsi yang dibuat selama rendering sebagai dependensi. Sebaiknya, deklarasikan fungsi tersebut di dalam Effect:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    function createOptions() {
      return {
        serverUrl: serverUrl,
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>Welcome to the {roomId} room!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Sekarang, Anda mendefinisikan fungsi createOptions di dalam Effect, sehingga Effect itu sendiri hanya tergantung pada string roomId. Dengan perbaikan ini, mengetik ke dalam input tidak akan membuat chat terhubung kembali. Berbeda dengan sebuah fungsi yang selalu dibuat ulang, sebuah string seperti roomId tidak berubah kecuali jika Anda menetapkannya ke nilai lain. Baca lebih lanjut tentang cara menghapus dependensi.


Membaca props dan state terbaru dari sebuah Effect

Dalam Pengembangan

Bagian ini menjelaskan sebuah API eksperimental yang belum dirilis dalam versi stabil React.

Secara default, ketika Anda membaca sebuah nilai reaktif dari sebuah Effect, Anda harus menambahkannya sebagai sebuah dependensi. Hal ini memastikan bahwa *Effect Anda “bereaksi” terhadap setiap perubahan dari nilai tersebut. Untuk sebagian besar dependensi, itulah perilaku yang Anda inginkan.

Namun, terkadang Anda akan ingin membaca props dan state terbaru dari sebuah Effect tanpa “bereaksi” terhadapnya. Sebagai contoh, bayangkan Anda ingin mencatat jumlah item dalam keranjang belanja untuk setiap kunjungan halaman:

function Page({ url, shoppingCart }) {
useEffect(() => {
logVisit(url, shoppingCart.length);
}, [url, shoppingCart]); // ✅ All dependencies declared
// ...
}

Bagaimana jika Anda ingin mencatat kunjungan halaman baru setelah setiap perubahan url, tetapi tidak jika hanya shoppingCart yang berubah? Anda tidak dapat mengabaikan shoppingCart dari dependensi tanpa melanggar aturan reaktivitas. Namun, Anda dapat menyatakan bahwa Anda “tidak ingin” suatu kode “bereaksi” terhadap perubahan meskipun itu dipanggil dari dalam sebuah Effect. Deklarasikan sebuah Effect Event dengan useEffectEvent Hook, dan pindahkan kode yang membaca shoppingCart ke dalamnya:

function Page({ url, shoppingCart }) {
const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, shoppingCart.length)
});

useEffect(() => {
onVisit(url);
}, [url]); // ✅ All dependencies declared
// ...
}

Effect Events tidak bersifat reaktif dan selalu harus diabaikan dari ketergantungan Effect Anda. Inilah yang memungkinkan Anda menempatkan kode non-reaktif (di mana Anda dapat membaca nilai terbaru dari beberapa props dan state) di dalamnya. Dengan membaca shoppingCart di dalam onVisit, Anda memastikan bahwa shoppingCart tidak akan menjalankan kembali Effect Anda.

Baca lebih lanjut tentang bagaimana Effect Events memungkinkan Anda memisahkan kode yang reaktif dan non-reaktif.


Menampilkan konten yang berbeda di server dan klien

Jika aplikasi Anda menggunakan server rendering (baik langsung maupun melalui framework), komponen Anda akan dirender di dua lingkungan yang berbeda. Di server, komponen akan dirender untuk menghasilkan HTML awal. Di klien, React akan menjalankan kode rendering lagi sehingga ia dapat melekatkan event handler Anda ke HTML tersebut. Oleh karena itu, agar hydrasi berfungsi, output render awal Anda harus identik di klien dan server.

Dalam kasus yang jarang terjadi, Anda mungkin perlu menampilkan konten yang berbeda di klien. Misalnya, jika aplikasi Anda membaca beberapa data dari localStorage, maka hal itu tidak mungkin dilakukan di server. Berikut adalah cara mengimplementasikannya:

function MyComponent() {
const [didMount, setDidMount] = useState(false);

useEffect(() => {
setDidMount(true);
}, []);

if (didMount) {
// ... return client-only JSX ...
} else {
// ... return initial JSX ...
}
}

Ketika aplikasi sedang dimuat, pengguna akan melihat output render awal. Kemudian, setelah aplikasi selesai dimuat dan dihydrate, Effect akan berjalan dan mengatur didMount menjadi true, memicu re-render. Ini akan beralih ke output render khusus client. Effect tidak berjalan di server, itulah sebabnya didMount bernilai false selama render awal di server.

Gunakan pola ini dengan bijak. Ingatlah bahwa pengguna dengan koneksi lambat akan melihat konten awal untuk waktu yang cukup lama - potensial, beberapa detik - jadi Anda tidak ingin membuat perubahan yang terlalu drastis pada tampilan komponen Anda. Dalam banyak kasus, Anda dapat menghindari kebutuhan ini dengan menunjukkan hal-hal yang berbeda secara kondisional dengan CSS.


Penyelesaian masalah

Effect Anda berjalan dua kali saat komponen terpasang

Ketika Strict Mode aktif di pengembangan, React menjalankan setup dan cleanup satu kali ekstra sebelum setup sebenarnya.

Ini adalah stress-test yang memverifikasi logika Effect Anda diimplementasikan dengan benar. Jika ini menyebabkan masalah yang terlihat, fungsi cleanup Anda kehilangan beberapa logika. Fungsi cleanup harus menghentikan atau membatalkan apa pun yang dilakukan fungsi setup. Aturan praktisnya adalah pengguna tidak boleh dapat membedakan antara setup yang dipanggil sekali (seperti dalam produksi) dan urutan setupcleanupsetup (seperti dalam pengembangan).

Baca lebih lanjut tentang bagaimana ini membantu menemukan bug dan cara memperbaiki logika Anda.


Effect Anda berjalan setelah setiap render ulang

Pertama, periksa apakah Anda telah lupa untuk menentukan array dependensi:

useEffect(() => {
// ...
}); // 🚩 No dependency array: re-runs after every render!

Jika Anda telah menentukan array dependensi tetapi Effect Anda masih berjalan dalam loop, itu karena salah satu dependensi Anda berbeda pada setiap re-render.

Anda dapat men-debug masalah ini dengan secara manual mencatat dependensi Anda ke konsol:

useEffect(() => {
// ..
}, [serverUrl, roomId]);

console.log([serverUrl, roomId]);

Anda dapat mengklik kanan pada array dari re-render yang berbeda di konsol dan pilih “Simpan sebagai variabel global” untuk keduanya. Anda kemudian dapat menggunakan konsol browser untuk memeriksa apakah setiap dependensi dalam kedua array tersebut sama:

Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays?
Object.is(temp1[1], temp2[1]); // Is the second dependency the same between the arrays?
Object.is(temp1[2], temp2[2]); // ... and so on for every dependency ...

Ketika Anda menemukan dependensi yang berbeda pada setiap re-render, biasanya Anda bisa memperbaikinya dengan salah satu cara berikut:

Sebagai upaya terakhir (jika metode-metode ini tidak membantu), bungkus pembuatannya dengan useMemo atau useCallback (untuk fungsi).


Effect Anda terus berjalan dalam siklus tak terbatas

Jika Effect Anda berjalan dalam siklus tak terbatas, dua hal ini harus benar:

  • Effect Anda memperbarui beberapa state.
  • State tersebut menyebabkan re-render, yang menyebabkan dependensi Effect berubah.

Sebelum Anda mulai memperbaiki masalah, tanyakan pada diri sendiri apakah Effect Anda terhubung ke sistem eksternal tertentu (seperti DOM, jaringan, widget pihak ketiga, dan sebagainya). Mengapa Effect Anda perlu menetapkan state? Apakah itu disinkronkan dengan sistem eksternal tersebut? Atau apakah Anda mencoba mengelola aliran data aplikasi Anda dengannya?

Jika tidak ada sistem eksternal, pertimbangkan untuk menghapus Effect sama sekali untuk menyederhanakan logika Anda.

Jika Anda benar-benar menyinkronkan dengan sistem eksternal, pertimbangkan mengapa dan dalam kondisi apa Effect Anda harus memperbarui state. Apakah ada sesuatu yang berubah yang mempengaruhi output visual komponen Anda? Jika Anda perlu melacak beberapa data yang tidak digunakan untuk merender, ref(yang tidak memicu re-render) mungkin lebih sesuai. Verifikasi bahwa Effect Anda tidak memperbarui state (dan memicu re-render) lebih dari yang dibutuhkan.

Akhirnya, jika Effect Anda memperbarui state pada waktu yang tepat, tetapi masih ada loop, itu karena pembaruan state tersebut menyebabkan salah satu dependensi Effect berubah. Baca cara memecahkan masalah perubahan dependensi.


Logika cleanup Anda berjalan meskipun komponen Anda tidak dilepas

Fungsi cleanup berjalan tidak hanya saat bongkar pasang, tetapi sebelum setiap re-render dengan dependensi yang berubah. Selain itu, dalam pengembangan, React menjalankan setup + cleanup satu kali tambahan segera setelah komponen dipasang.

Jika Anda memiliki kode cleanup tanpa kode setup yang sesuai, biasanya itu adalah tanda masalah dalam kode:

useEffect(() => {
// 🔴 Avoid: Cleanup logic without corresponding setup logic
return () => {
doSomething();
};
}, []);

Logika cleanup Anda harus “simetris” dengan logika setup awal, dan harus menghentikan atau mengembalikan apa yang dilakukan oleh setup awal:

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);

Pelajari bagaimana siklus hidup Effect berbeda dari siklus hidup komponen.


Effect Anda melakukan sesuatu yang visual, dan Anda melihat kedipan sebelum berjalan

Jika Effect Anda harus menghalangi browser agar tidak mengecat layar, ganti useEffect dengan useLayoutEffect. Perhatikan bahwa ini seharusnya tidak diperlukan untuk sebagian besar Effect. Anda hanya memerlukannya jika sangat penting untuk menjalankan Effect Anda sebelum browser melukis: misalnya, untuk mengukur dan memposisikan tooltip sebelum pengguna melihatnya.