Урок 09

useLayoutEffect и useId

useLayoutEffect

useLayoutEffect похож на useEffect, но выполняется синхронно после мутаций DOM и до отрисовки браузером.

Отличие от useEffect

  • useEffect — асинхронно, после отрисовки
  • useLayoutEffect — синхронно, до отрисовки

Когда нужен

Когда нужно измерить DOM или синхронно изменить разметку до того, как пользователь увидит мерцание.

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

function Tooltip({ target }: { target: HTMLElement | null }) {
    const [position, setPosition] = useState({ top: 0, left: 0 });
    const tooltipRef = useRef<HTMLDivElement>(null);

    useLayoutEffect(() => {
        if (!target || !tooltipRef.current) return;

        const rect = target.getBoundingClientRect();

        setPosition({
            top: rect.bottom + 8,
            left: rect.left,
        });
    }, [target]);

    return (
        <div ref={tooltipRef} style={{ position: 'absolute', ...position }}>
            Tooltip
        </div>
    );
}

Предупреждение

useLayoutEffect блокирует отрисовку. Если внутри тяжёлое вычисление — пользователь увидит задержку. Используй только когда нужен синхронный доступ к DOM.

SSR

На сервере useLayoutEffect не работает и выдаёт предупреждение. Если компонент рендерится на сервере — используй useEffect.

useId

useId генерирует уникальный ID, который стабилен между рендерами. Полезен для связки label и input.

Проблема

function Input({ label }: { label: string }) {
    return (
        <>
            <label htmlFor="input">Заголовок</label>
            <input id="input" />
        </>
    );
}

Если таких компонентов несколько — ID конфликтуют.

Решение

import { useId } from 'react';

function Input({ label }: { label: string }) {
    const id = useId();

    return (
        <>
            <label htmlFor={id}>{label}</label>
            <input id={id} />
        </>
    );
}

Каждый экземпляр получит свой ID.

Особенности

  • ID стабилен между рендерами
  • ID уникален в пределах приложения
  • Не используй для key
  • Работает и на сервере, и на клиенте
const id = useId();
const nameId = `${id}-name`;
const emailId = `${id}-email`;

Итоги

  • useLayoutEffect — синхронно до отрисовки
  • Нужен для измерения DOM
  • Блокирует рендер — используй осторожно
  • useId генерирует стабильные уникальные ID
  • Для связи label и input