Trước React 19, callback ref được gọi hai lần trong vòng đời: khi gắn thì nhận node, khi gỡ (hoặc khi hàm ref đổi identity) thì nhận null.
Muốn dọn dẹp phải tự kiểm tra null và giữ tham chiếu để gỡ listener:
jsx
const ref = useCallback((node) => {
if (node) {
observer.observe(node)
} else {
observer.disconnect() // cleanup lives in the null branch
}
}, [])Từ React 19, callback ref có thể trả về hàm cleanup, React sẽ gọi khi node bị gỡ — cùng mô hình với useEffect:
jsx
<input ref={(node) => {
const observer = new ResizeObserver(onResize)
observer.observe(node)
return () => observer.disconnect()
}} />Breaking change: khi callback ref có giá trị trả về, React không gọi lại nó với null nữa.
- Code cũ dựa vào nhánh
nullđể dọn dẹp sẽ không chạy nữa nếu vô tình trả về một giá trị. - Cạm bẫy thực tế là arrow function rút gọn:
jsx
// wrong: returns the assignment result, cleanup contract broken
<div ref={(node) => (myRef.current = node)} />
// right: block body, returns undefined
<div ref={(node) => { myRef.current = node }} />Đây là lý do quy tắc lint no-implicit-ref-callback-return xuất hiện trong bản 19 và codemod chính thức sửa đúng dạng viết tắt này.