Khác biệt duy nhất là thời điểm chạy so với lúc trình duyệt vẽ (paint):
useEffect: chạy sau khi trình duyệt đã paint, bất đồng bộ — không chặn hiển thị. Đúng cho ~99% trường hợp: fetch, subscription, log, đồng bộ với hệ thống ngoài.useLayoutEffect: chạy đồng bộ ngay sau khi React cập nhật DOM, trước khi paint. React đảm bảo: nếu trong đó bạn setState, React render lại và trình duyệt chỉ paint kết quả cuối — người dùng không thấy frame trung gian.
Dùng useLayoutEffect khi cần đo layout rồi điều chỉnh trước khi hiển thị: đo getBoundingClientRect để định vị tooltip/popover (lật hướng nếu tràn màn hình), đồng bộ scroll position. Nếu làm việc này bằng useEffect sẽ thấy nháy hình (flicker) — render sai vị trí một frame rồi mới nhảy về đúng.
Đánh đổi: vì chạy đồng bộ và chặn paint, logic nặng trong useLayoutEffect làm chậm cảm nhận toàn app — mặc định dùng useEffect, chỉ đổi khi thấy flicker thật. Với SSR, useLayoutEffect không chạy trên server và React cảnh báo — vì chưa có DOM để đo.