<StrictMode> là một component của React, bật thêm các bước kiểm tra cho mọi thứ nằm bên trong nó, chỉ ở môi trường development. Nó render mỗi component hai lần, chạy setup, cleanup rồi setup lại cho mọi effect khi mount, chạy ref callback hai lần, và cảnh báo các API đã lỗi thời. Đó là lý do useEffect "chạy hai lần" ở development. Trong bản build production, nó không làm gì cả.
Bạn bật nó bằng cách bọc ứng dụng, thường là trong main.jsx:
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
);
Các bản xem trước trên trang này chạy như bản build production, nên StrictMode sẽ không có tác dụng nào nhìn thấy được ở đây. Thay vào đó, mỗi ví dụ bên dưới chứa đúng loại bug mà StrictMode sinh ra để bắt, và để bạn tự tay kích hoạt nó. Ở đây một effect thêm listener click vào document và không bao giờ gỡ nó ra. Ẩn rồi hiện panel vài lần, sau đó click vào trang.
Mỗi lần nhấn Show lại thêm một listener, và không cái nào bị gỡ, nên một cú click ghi log một lần cho mỗi lần mount. Đó là rò rỉ bộ nhớ mà người dùng có thể gây ra chỉ bằng cách mở rồi đóng một panel. StrictMode sẽ cho bạn thấy ngay từ lần mount đầu tiên: ở development, nó mount, unmount rồi mount lại, nên bạn sẽ thấy hai dòng log cho mỗi cú click trước cả khi chạm vào nút.
Effect: setup, cleanup, setup
Khi mount, StrictMode chạy effect của bạn, rồi cleanup của nó, rồi effect thêm lần nữa. Nó đang giả lập chuyện xảy ra khi người dùng rời một màn hình rồi quay lại, và kiểm tra rằng cleanup hoàn tác đúng những gì setup đã làm. Nếu đúng vậy, lần setup thứ hai để lại mọi thứ y như chỉ có một lần setup, và bạn không thấy gì sai. Nếu không, bug lộ ra ngay lập tức.
Cách sửa cho logger là một cleanup gỡ đúng hàm mà nó đã thêm vào:
Bật tắt bao nhiêu lần tùy thích: mỗi cú click chỉ ghi một dòng khi logger đang hiện. Dưới StrictMode ở development, console sẽ bắt đầu bằng setup, cleanup, setup, và ứng dụng vẫn chạy đúng. Chuỗi đó là dấu hiệu của một effect khỏe mạnh, không phải vấn đề cần tắt đi.
Những effect thường cần cleanup: event listener, setInterval và setTimeout, subscription, kết nối WebSocket hoặc chat, và các request mà response cần bị bỏ qua nếu component đã biến mất. Trang useEffect trình bày mẫu cleanup cho từng trường hợp.
Render: được gọi hai lần
StrictMode cũng gọi hàm component của bạn hai lần cho mỗi lần render (cùng với các hàm bạn truyền cho useState, useMemo và useReducer, và constructor, render, shouldComponentUpdate của class). React giữ kết quả của một lần gọi và bỏ lần còn lại. Một component thuần trả về cùng một JSX ở cả hai lần, nên không có gì thay đổi.
Component thay đổi thứ gì đó bên ngoài nó trong lúc render là component không thuần, và gọi nó hai lần khiến thay đổi đó xảy ra hai lần. Component này thêm một mục "rendered at" vào mảng mà nó nhận qua prop:
Mỗi lần render lại thêm một mục mà không ai yêu cầu. Ở đây bạn chỉ nhận ra sau khi click. Dưới StrictMode, ngay lần render đầu tiên đã hiện hai mục mới thay vì một. Cách sửa là tính toán trong lúc render mà không thay đổi dữ liệu gốc: const shown = [...entries, 'extra'].
React 19 thêm một chi tiết: trong lần render kép, useMemo và useCallback dùng lại kết quả của lần gọi đầu thay vì tính hai lần.
Ref callback
Từ React 19, StrictMode cũng chạy ref callback hai lần khi mount: gắn, cleanup, gắn. Một ref callback thêm listener hoặc đăng ký node ở đâu đó cần trả về một cleanup, giống hệt một effect:
<div
ref={(node) => {
const observer = new ResizeObserver(onResize);
observer.observe(node);
return () => observer.disconnect();
}}
/>
Cảnh báo API lỗi thời
StrictMode cảnh báo các API sắp bị loại bỏ, chủ yếu trong class component, như các lifecycle cũ UNSAFE_componentWillMount, UNSAFE_componentWillReceiveProps và UNSAFE_componentWillUpdate. Một số API từng bị nó cảnh báo, như string ref và findDOMNode, đã bị xóa hẳn khỏi React 19.
Bật cho một phần ứng dụng
<StrictMode> là một component bình thường, nên bạn có thể chỉ bọc một phần của cây. Cách này hữu ích khi áp dụng cho một ứng dụng lớn: bắt đầu từ các màn hình mới rồi mở rộng dần.
function App() {
return (
<>
<Header />
<StrictMode>
<Sidebar />
<Content />
</StrictMode>
<Footer />
</>
);
}
Sidebar và Content (cùng mọi thứ chúng render) được kiểm tra; Header và Footer thì không. Không có cách nào tắt nó cho một cây con nằm bên trong phần đang bật strict.
Nơi nó được bật sẵn theo mặc định:
- Vite: template React bọc
<App />trong<StrictMode>ởmain.jsx. - Next.js: App Router bật nó theo mặc định;
reactStrictModetrongnext.config.jsđiều khiển nó.
Ở production, nó không làm gì cả
Bản build production bỏ qua <StrictMode>: một lần render, một lần chạy effect, không cảnh báo. Vì vậy các lần gọi kép không bao giờ làm người dùng của bạn tốn gì, và không có lý do gì để gỡ lớp bọc trước khi triển khai.
Nếu effect chạy kép làm hỏng thứ gì đó ở development, chẳng hạn một request bị gửi hai lần hay một sự kiện analytics bị ghi hai lần, đó là thông tin đáng giá. Phần lớn trường hợp, cách sửa là một cleanup (cờ ignore cho request, ngắt kết nối cho connection). Với việc thật sự chỉ được xảy ra một lần mỗi khi ứng dụng tải, như khởi tạo một SDK, hãy chạy nó bên ngoài mọi component ở cấp module, hoặc chặn nó bằng một cờ ở cấp module.
let didInit = false;
function App() {
useEffect(() => {
if (didInit) return;
didInit = true;
initAnalytics(); // runs once per page load, even in StrictMode
}, []);
}
Tránh cách lách phổ biến là dùng một ref để bỏ qua lần chạy effect thứ hai. Nó giấu cảnh báo đi mà không sửa cleanup bị thiếu, và bug sẽ quay lại ngay lần đầu component thật sự unmount rồi mount lại, như logger ở trên.
Câu hỏi thường gặp
React StrictMode dùng để làm gì?
Chỉ ở development, nó render mọi component bên trong hai lần, chạy setup, cleanup rồi setup lại cho từng effect khi component mount, chạy ref callback hai lần theo cùng cách đó, và cảnh báo các API đã lỗi thời. Những lần gọi thêm này làm lộ ra các lần render không thuần và các effect thiếu cleanup.
Vì sao useEffect chạy hai lần?
Vì component nằm trong <StrictMode> và bạn đang chạy bản build development. React mount component, giả lập một lần unmount (cleanup của bạn chạy), rồi mount lại. Nếu lần setup thứ hai gây ra bug, effect đó đang thiếu cleanup.
StrictMode có ảnh hưởng đến production không?
Không. Bản build production render một lần và chạy mỗi effect một lần, dù có hay không có <StrictMode>. Bạn cứ để nó trong code.
Làm sao để tắt StrictMode?
Bỏ lớp bọc <StrictMode>, thường nằm trong main.jsx hoặc index.js, hoặc đặt reactStrictMode: false trong next.config.js. Nhưng sửa cleanup thường là cách tốt hơn: lần chạy kép đang chỉ ra một bug mà người dùng thật có thể gặp.
Vì sao console.log được in ra hai lần?
Ở development, StrictMode gọi hàm component của bạn hai lần cho mỗi lần render. Trong phần cài đặt của React DevTools, bạn có thể làm mờ log của lần gọi thứ hai hoặc ẩn hẳn chúng đi.