useLayoutEffect

Pitfall

useLayoutEffect dapat mempengaruhi kinerja. Lebih baik gunakan useEffect bila memungkinkan.

useLayoutEffect adalah versi useEffect yang dijalankan sebelum peramban melukis ulang (repaint) layar.

useLayoutEffect(setup, dependencies?)

Referensi

useLayoutEffect(setup, dependencies?)

Panggil useLayoutEffect untuk melakukan pengukuran tata letak sebelum peramban melukis ulang layar:

import { useState, useRef, useLayoutEffect } from 'react';

function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);

useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height);
}, []);
// ...

Lihat contoh lainnya di bawah ini

Parameter

  • setup: Fungsi dengan logika Efek Anda. Fungsi setup juga dapat secara opsional mengembalikan fungsi pembersihan (cleanup). Sebelum komponen pertama kali ditambahkan ke DOM, React akan menjalankan fungsi setup. Setelah setiap re-render dengan dependensi yang berubah, React akan terlebih dahulu menjalankan fungsi pembersihan (jika Anda memberikannya) dengan nilai lama. Selanjutnya, React akan menjalankan fungsi setup dengan nilai baru. Sebelum komponen dihapus dari DOM, React akan menjalankan fungsi pembersihan untuk terakhir kali.

  • opsional dependencies: Daftar semua nilai reaktif yang dirujuk di dalam kode setup. Nilai reaktif termasuk props, state, dan semua variabel dan fungsi dideklarasikan langsung di dalam komponen Anda. Jika linter Anda telah dikonfigurasi untuk React, maka linter tersebut akan memverifikasi bahwa setiap nilai reaktif sudah diatur dengan benar sebagai dependensi. Daftar dependensi ini harus memiliki jumlah item yang konstan dan ditulis secara inline seperti [dep1, dep2, dep3]. React akan membandingkan setiap dependensi dengan nilai lama menggunakan perbandingan Object.is. Jika argumen ini diabaikan, efek akan dijalankan ulang setelah setiap re-render dari komponen.

Returns

useLayoutEffect mengembalikan undefined.

Caveats

  • useLayoutEffect adalah sebuah Hook, sehingga Anda hanya dapat memanggilnya di tingkat atas komponen ataupun di custom Hooks Anda. Serta Anda tidak dapat memanggilnya di dalam perulangan ataupun percabangan. Bila diperlukan, ekstrak komponen dan pindahkan Efek ke dalam komponen tersebut.

  • Ketika Strict Mode aktif, React akan menjalankan siklus setup+pembersihan khusus pengembangan (development-only) sebelum menjalankan setup sebenarnya. Uji ketahanan (Stress-test) tersebut memastikan logika pembersihan “mencerminkan” logika setup dan pembersihan tersebut dapat menghentikan atau membatalkan apa pun yang sedang dilakukan fungsi setup. Jika hal ini menyebabkan masalah, maka implementasikan fungsi pembersihan.

  • Jika beberapa dependensi merupakan objek atau fungsi yang didefinisikan di dalam komponen, ada risiko Efek akan dijalankan berulang kali lebih sering dari yang dibutuhkan. Untuk memperbaiki ini, hilangkan dependensi objek dan fungsi yang tidak dibutuhkan. Anda juga dapat mengekstrak pembaruan state dan logika non-reaktif diluar dari efek Anda.

  • Efek hanya berjalan di sisi klien. Efek tidak berjalan ketika server rendering.

  • Kode di dalam useLayoutEffect serta semua pembaruan state yang telah dijadwalkan akan menghalangi peramban untuk melukis ulang layar. Penggunaan yang berlebihan dapat menyebabkan aplikasi Anda lambat. Jika memungkinkan, gunakan useEffect.


Penggunaan

Mengukur tata letak sebelum peramban melukis ulang layar

Sebagian besar komponen tidak perlu mengetahui posisi dan ukuran di layar untuk memutuskan apa yang harus dirender. Komponen hanya mengembalikan beberapa JSX. Selanjutnya, peramban akan mengukur tata letak (posisi dan ukuran) dan melukis ulang layar

Terkadang, itu tidak cukup. Bayangkan sebuah tooltip berada di sebelah elemen tertentu saat diarahkan (hover). Jika ruang mencukupi, posisi tooltip harus berada di atas elemen tersebut. Tetapi, jika tidak cukup, posisi tooltip harus berada di bawah. Untuk merender tooltip di posisi akhir yang tepat, maka Anda harus mengetahui tingginya (yaitu, apakah muat berada di atas)

Untuk melakukan hal ini, Anda perlu merender dalam dua tahap:

  1. Merender tooltip di mana saja (bahkan dengan posisi yang salah).
  2. Mengukur tingginya dan menentukan posisi tooltip tersebut.
  3. Merender ulang tooltip agar berada di posisi yang tepat.

Seluruh proses tersebut harus terjadi sebelum peramban melukis ulang layar. Anda tidak ingin pengguna untuk melihat tooltip bergerak. Panggil useLayoutEffect untuk melakukan pengukuran tata letak sebelum peramban melukis ulang layar:

function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // Belum mengetahui tinggi tooltip sebenarnya

useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // Lakukan re-render setelah mengetahui tinggi tooltip
}, []);

// ...gunakan tooltipHeight di logika render di bawah ini ...
}

Berikut adalah langkah-langkah cara kerja:

  1. Tooltip dirender dengan menginisialisasi nilai tooltipHeight = 0 (Sehingga, memungkinkan tooltip berada di posisi yang salah).
  2. React menempatkannya di DOM dan menjalankan kode di useLayoutEffect.
  3. useLayoutEffectmengukur tinggi konten tooltip dan akan segera memicu re-render.
  4. Tooltip dirender ulang dengan nilai tooltipHeight yang sebenarnya (sehingga tooltip berada di posisi yang benar).
  5. React memperbarui DOM dan akhirnya peramban menampilkan tooltip tersebut.

Arahkan kursor ke tombol-tombol berikut dan perhatikan tooltip menyesuaikan posisinya tergantung dari muat atau tidaknya ruang:

import { useRef, useLayoutEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import TooltipContainer from './TooltipContainer.js';

export default function Tooltip({ children, targetRect }) {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useLayoutEffect(() => {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height);
    console.log('Hasil pengukuran tinggi tooltip: ' + height);
  }, []);

  let tooltipX = 0;
  let tooltipY = 0;
  if (targetRect !== null) {
    tooltipX = targetRect.left;
    tooltipY = targetRect.top - tooltipHeight;
    if (tooltipY < 0) {
      // Tooltip tidak muat di atas, maka ditempatkan di bawah
      tooltipY = targetRect.bottom;
    }
  }

  return createPortal(
    <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}>
      {children}
    </TooltipContainer>,
    document.body
  );
}

Perhatikan meskipun komponen Tooltip harus dirender dalam dua tahap (pertama, dengan nilai tooltipHeight diinisialisasi 0 dan ketika nilai tersebut diukur sesuai dengan tinggi sebenarnya), Anda hanya melihat hasil akhirnya. Ini sebabnya mengapa Anda menggunakan useLayoutEffect dibandingkan useEffect untuk kasus contoh tersebut. Mari kita lihat perbedaanya secara detail di bawah ini.

useLayoutEffect vs useEffect

Example 1 of 2:
useLayoutEffect menghalangi peramban untuk melukis ulang

React menjamin kode di dalam useLayoutEffect dan setiap pembaruan state yang dijadwalkan akan diproses sebelum peramban melukis ulang layar. Hal ini memungkinkan Anda untuk merender tooltip, mengukurnya, dan merender ulang kembali tooltip tersebut tanpa pengguna menyadari render awal tambahan. Dengan kata lain, useLayoutEffect menghalangi peramban untuk melukis ulang

import { useRef, useLayoutEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import TooltipContainer from './TooltipContainer.js';

export default function Tooltip({ children, targetRect }) {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useLayoutEffect(() => {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height);
  }, []);

  let tooltipX = 0;
  let tooltipY = 0;
  if (targetRect !== null) {
    tooltipX = targetRect.left;
    tooltipY = targetRect.top - tooltipHeight;
    if (tooltipY < 0) {
      // Tooltip tidak muat di atas, maka ditempatkan di bawah
      tooltipY = targetRect.bottom;
    }
  }

  return createPortal(
    <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}>
      {children}
    </TooltipContainer>,
    document.body
  );
}

Note

Proses merender dalam dua tahap dan memblokir peramban akan menurunkan kinerja. Cobalah untuk menghindari ini sebisa mungkin.


Pemecahan Masalah

Saya menerima pesan kesalahan: ”useLayoutEffect does nothing on the server”

Tujuan dari useLayoutEffect adalah memungkinkan sebuah komponen menggunakan informasi tata letak untuk merender:

  1. Merender konten awal.
  2. Mengukur tata letak sebelum peramban melukis ulang layar.
  3. Merender konten akhir menggunakan informasi tata letak yang telah dibaca.

Saat Anda atau framework Anda menggunakan server rendering, aplikasi React Anda dirender menjadi HTML di server saat awal merender.

Masalahnya, di server tidak tersedia informasi tentang tata letak.

Pada contoh sebelumnya, pemanggilan useLayoutEffect pada komponen Tooltip memungkinkan posisi tooltip disesuaikan dengan benar (entah di atas atau di bawah konten) tergantung pada tinggi konten. Jika Anda mencoba merender Tooltip sebagai bagian dari HTML server awal, ini akan menjadi tidak mungkin untuk ditentukan. Pada server, belum terdapat tata letak! Jadi, meskipun Anda merendernya pada server, posisinya akan “melompat” di klien setelah JavaScript dimuat dan dijalankan.

Biasanya, komponen yang bergantung pada informasi tata letak tidak perlu dirender di server. Misalnya, mungkin tidak masuk akal untuk menampilkan Tooltip selama render awal. Itu dipicu oleh interaksi klien.

Namun, jika Anda mengalami masalah ini, ada beberapa opsi yang tersedia:

  • Ubah useLayoutEffect menjadi useEffect. Hal ini memberi tahu React bahwa hasil render awal dapat ditampilkan tanpa memblokir proses melukis (karena HTML asli akan menjadi terlihat sebelum Efek dijalankan).

  • Sebagai alternatif, [tandai komponen Anda sebagai client-only.](/reference/react/Suspense#providing-a-fallback-for-server-errors-and-server-only-content Ini memberi tahu React untuk mengganti kontennya hingga batas <Suspense> terdekat dengan loading cadangan (misalnya, spinner atau glimmer) selama server rendering.

  • Sebagai alternatif, Anda dapat merender komponen dengan useLayoutEffect hanya setelah proses hidrasi. Pertahankan state boolean isMounted yang diinisialisasi dengan nilai false, dan atur nilainya menjadi true di dalam panggilan useEffect. Logika rendering Anda dapat dituliskan seperti ini: return isMounted ? <RealContent /> : <FallbackContent />. Selama di sisi server atau proses hidrasi, pengguna akan melihat FallbackContent yang tidak memanggil useLayoutEffect. Kemudian React akan menggantinya dengan RealContent yang hanya dijalankan di sisi klien dan dapat menyertakan pemanggilan useLayoutEffect.

  • Jika Anda menyinkronkan komponen Anda dengan penyimpanan data eksternal dan mengandalkan useLayoutEffect untuk penggunaan selain dari pengukuran tata letak, pertimbangkan useSyncExternalStore sebagai gantinya, yang mendukung server rendering.