useLayoutEffect
useLayoutEffect
adalah versi useEffect
yang dijalankan sebelum browser melukis ulang (repaint) layar.
useLayoutEffect(setup, dependencies?)
Referensi
useLayoutEffect(setup, dependencies?)
Panggil useLayoutEffect
untuk melakukan pengukuran tata letak sebelum browser 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 kodesetup
. 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 perbandinganObject.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
dan semua pembaruan state yang dijadwalkan akan menghalangi browser untuk melukis ulang layar. Penggunaan yang berlebihan dapat menyebabkan aplikasi Anda lambat. Jika memungkinkan, gunakanuseEffect
.
Penggunaan
Mengukur tata letak sebelum browser 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, browser 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 pada 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:
- Merender tooltip di mana saja (bahkan dengan posisi yang salah).
- Mengukur tingginya dan menentukan posisi tooltip tersebut.
- Merender ulang tooltip agar berada di posisi yang tepat.
Seluruh proses tersebut harus terjadi sebelum browser melukis ulang layar. Anda tidak ingin pengguna untuk melihat tooltip bergerak. Panggil useLayoutEffect
untuk melakukan pengukuran tata letak sebelum browser 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:
Tooltip
dirender dengan menginisialisasi nilaitooltipHeight = 0
(Sehingga, memungkinkan tooltip berada di posisi yang salah).- React menempatkannya di DOM dan menjalankan kode di
useLayoutEffect
. useLayoutEffect
mengukur tinggi kontentooltip
dan akan segera memicu re-render.Tooltip
dirender ulang dengan nilaitooltipHeight
yang sebenarnya (sehingga tooltip berada di posisi yang benar).- React memperbarui DOM dan akhirnya browser 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.
Example 1 of 2: useLayoutEffect
menghalangi browser untuk melukis ulang
React menjamin kode di dalam useLayoutEffect
dan setiap pembaruan state yang dijadwalkan akan diproses sebelum browser 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 browser 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 ); }
Troubleshooting
I’m getting an error: ”useLayoutEffect
does nothing on the server”
The purpose of useLayoutEffect
is to let your component use layout information for rendering:
- Render the initial content.
- Measure the layout before the browser repaints the screen.
- Render the final content using the layout information you’ve read.
When you or your framework uses server rendering, your React app renders to HTML on the server for the initial render. This lets you show the initial HTML before the JavaScript code loads.
The problem is that on the server, there is no layout information.
In the earlier example, the useLayoutEffect
call in the Tooltip
component lets it position itself correctly (either above or below content) depending on the content height. If you tried to render Tooltip
as a part of the initial server HTML, this would be impossible to determine. On the server, there is no layout yet! So, even if you rendered it on the server, its position would “jump” on the client after the JavaScript loads and runs.
Usually, components that rely on layout information don’t need to render on the server anyway. For example, it probably doesn’t make sense to show a Tooltip
during the initial render. It is triggered by a client interaction.
However, if you’re running into this problem, you have a few different options:
-
Replace
useLayoutEffect
withuseEffect
. This tells React that it’s okay to display the initial render result without blocking the paint (because the original HTML will become visible before your Effect runs). -
Alternatively, mark your component as client-only. This tells React to replace its content up to the closest
<Suspense>
boundary with a loading fallback (for example, a spinner or a glimmer) during server rendering. -
Alternatively, you can render a component with
useLayoutEffect
only after hydration. Keep a booleanisMounted
state that’s initialized tofalse
, and set it totrue
inside auseEffect
call. Your rendering logic can then be likereturn isMounted ? <RealContent /> : <FallbackContent />
. On the server and during the hydration, the user will seeFallbackContent
which should not calluseLayoutEffect
. Then React will replace it withRealContent
which runs on the client only and can includeuseLayoutEffect
calls. -
If you synchronize your component with an external data store and rely on
useLayoutEffect
for different reasons than measuring layout, consideruseSyncExternalStore
instead which supports server rendering.