Lỗi Gặp Phải
Bạn gắn ref vào một component tùy chỉnh và thấy thông báo này trong console:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Code của bạn có thể trông như thế này:
function TextInput({ placeholder }) {
return <input placeholder={placeholder} />;
}
// Ở nơi khác
const inputRef = useRef(null);
<TextInput ref={inputRef} placeholder="Type here" />
Ref không làm gì cả một cách âm thầm. inputRef.current vẫn là null, React ghi lại cảnh báo đó, và mọi thứ không bị lỗi rõ ràng — khiến bug này rất dễ bị bỏ sót.
Nguyên Nhân
ref không phải là một prop thông thường trong React. Khi bạn viết ref={inputRef} trên một function component, React sẽ chặn nó lại trước khi nó kịp đến component của bạn — nó không bao giờ xuất hiện trong props. Vì function component thuần túy không có instance, không có chỗ nào để gắn ref, nên React bỏ qua nó hoàn toàn.
Class component không gặp vấn đề này vì chúng có instance. Function component thì không. Đó là lý do React.forwardRef() ra đời — đây là cách opt-in tường minh để báo cho React biết: "component này biết cách xử lý ref."
Cách Sửa: Bọc bằng React.forwardRef
Bọc component của bạn bằng forwardRef. React sau đó sẽ truyền ref như là tham số thứ hai vào hàm của bạn — tách biệt với props — và bạn gắn nó vào DOM element mà bạn muốn expose.
Trước (bị lỗi)
function TextInput({ placeholder }) {
return <input placeholder={placeholder} />;
}
Sau (đã sửa)
import { forwardRef } from 'react';
const TextInput = forwardRef(function TextInput({ placeholder }, ref) {
return <input ref={ref} placeholder={placeholder} />;
});
export default TextInput;
Component cha giờ có thể truy cập trực tiếp vào DOM node <input> bên dưới:
import { useRef } from 'react';
import TextInput from './TextInput';
function Form() {
const inputRef = useRef(null);
function focusInput() {
inputRef.current?.focus();
}
return (
<>
<TextInput ref={inputRef} placeholder="Type here" />
<button onClick={focusInput}>Focus</button>
</>
);
}
Dùng Arrow Function (cũng hợp lệ)
const TextInput = forwardRef(({ placeholder }, ref) => (
<input ref={ref} placeholder={placeholder} />
));
TextInput.displayName = 'TextInput';
Luôn đặt displayName cho các component dạng arrow function. Nếu không, React DevTools sẽ gắn nhãn component là ForwardRef — không sao cho đến khi bạn debug một component tree với năm cái xếp chồng lên nhau.
Chuyển Tiếp Ref đến Element Lồng Nhau
Ref không nhất thiết phải gắn vào element gốc. Đặt nó ở bất kỳ đâu mà người gọi thực sự cần truy cập:
const Card = forwardRef(function Card({ children, className }, ref) {
return (
<div className="card-wrapper">
<div ref={ref} className={`card ${className}`}>
{children}
</div>
</div>
);
});
TypeScript: Khai Báo Kiểu forwardRef Đúng Cách
Cú pháp generic là forwardRef<RefType, PropsType>. Cả hai tham số kiểu đều quan trọng:
import { forwardRef, InputHTMLAttributes } from 'react';
interface TextInputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string;
}
const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
function TextInput({ label, ...props }, ref) {
return (
<div>
{label && <label>{label}</label>}
<input ref={ref} {...props} />
</div>
);
}
);
export default TextInput;
Bỏ qua các generic và TypeScript sẽ suy luận ref là unknown. Điều này gây ra lỗi kiểu dữ liệu ở bất kỳ đâu ref được dùng về sau — thường là ở một file hoàn toàn khác, khiến việc truy tìm nguyên nhân gốc rễ rất phiền phức.
Lỗi Thường Gặp: Quên Gắn Ref
Lỗi này làm khó nhiều người. Bạn bọc bằng forwardRef, cảnh báo trong console biến mất, mọi thứ trông ổn. Nhưng ref.current vẫn là null:
// BUG: ref argument received but never used
const TextInput = forwardRef(function TextInput({ placeholder }, ref) {
return <input placeholder={placeholder} />; // ref not attached!
});
Nhận ref như một tham số chỉ mới hoàn thành một nửa công việc. Bạn phải thực sự truyền nó cho một DOM element hoặc một component forwardRef khác thì mọi thứ mới hoạt động.
Kiểm Tra Xem Đã Sửa Đúng Chưa
- Cảnh báo đã biến mất khỏi console trình duyệt của bạn.
inputRef.currentkhông còn lànullsau khi mount. Thêm một dòng loguseEffectnhanh để xác nhận:
useEffect(() => {
console.log('ref:', inputRef.current); // Should print the DOM element, e.g. <input>
}, []);
- React DevTools hiển thị component theo tên (
TextInput, không phảiForwardRef) — điều này xác nhậndisplayNameđã được đặt. - Các lệnh gọi trực tiếp như
.focus()hay.scrollIntoView()hoạt động mà không báo lỗi.
Tóm Tắt Nhanh
- Function component thường + prop
ref→ cảnh báo, ref lànull - Bọc bằng
forwardRef→ tham số thứ hai là ref, gắn nó vào một DOM node - TypeScript: dùng generic
forwardRef<HTMLElement, Props>— cả hai đều quan trọng - Đặt
displayNamecho các arrow-function component để DevTools hiển thị tên dễ đọc - Cảnh báo hết nhưng ref vẫn là
null? Bạn đã bọc đúng nhưng quên gắn ref bên trong

