Phân biệt bằng ba dấu hiệu: chỉ xảy ra ở môi trường phát triển, mỗi lần mount sinh thêm đúng một lần chạy, và biến mất khi bỏ <StrictMode> ra. Nếu thiếu bất kỳ dấu hiệu nào thì nguyên nhân nằm ở chỗ khác.
Cách kiểm nhanh, theo thứ tự:
1. Build production rồi chạy thử. Còn gọi hai lần thì không phải Strict Mode.
2. Đếm số lần trên MỘT lần mount. Strict Mode thêm đúng một lần chạy cho mỗi lần mount, nên một lần mount cho ra hai request. Thấy bốn, sáu, hay gọi liên tục thì component đang remount hoặc dependency đang đổi.
3. Xem tab Network. Hai request cùng URL cách nhau vài mili giây là dấu hiệu của Strict Mode, chưa phải kết luận — hai component cùng fetch một URL cũng cho ra hình ảnh y hệt. Chốt lại bằng bước 1. Cách nhau theo thao tác người dùng thì là dependency đổi.
Khi không phải Strict Mode, ba nguyên nhân hay gặp:
// a. Dependency là object dựng mới mỗi render → effect chạy lại mỗi render
useEffect(() => { load(filters) }, [filters])
// b. Component cha remount vì key đổi hoặc bị render có điều kiện
{isOpen && <Panel />} // đóng/mở là mount lại từ đầu
// c. Hai component cùng gọi một hook fetch, không hề biết nhauVới nguyên nhân (a), đưa giá trị nguyên thuỷ vào deps hoặc bọc useMemo. Với (c), đó chính là việc mà lớp cache của TanStack Query hay SWR giải quyết: cùng một key thì gộp thành một request.
Lưu ý: Nếu xác định là Strict Mode thì đừng chặn nó bằng cờ useRef. Nó chạy hai lần để lộ ra effect thiếu dọn dẹp; chặn đi là giấu triệu chứng, còn request không huỷ vẫn là race condition chờ xảy ra ở production.