forwardRef cho phép component cha truyền một ref qua component của bạn đến một phần tử DOM bên trong nó, để component cha có thể gọi focus(), scrollIntoView() và các phương thức tương tự trên phần tử đó. Ở React 19 bạn không cần nó nữa: function component nhận ref như một prop bình thường. forwardRef vẫn hoạt động, và bạn sẽ gặp nó trong phần lớn code hiện có, nên trang này trình bày cả hai cách.
forwardRef nhận một hàm render có hai đối số: props, và ref mà component cha truyền vào. ref không nằm trong props; nó được truyền riêng. Click Focus và console xác nhận inputRef.current chính là nút <input> thật.
Cách của React 19: ref là một prop
Từ React 19, function component nhận ref trong props như mọi prop khác. Không cần bọc, không có đối số thứ hai:
Gõ gì đó rồi click Log value: component cha đọc văn bản hiện tại của ô nhập trực tiếp từ nút DOM. Xóa ref={ref} khỏi <input> rồi click Focus: console hiện lỗi, vì giờ inputRef.current là null.
forwardRef (mọi phiên bản) | Prop ref (React 19 trở lên) | |
|---|---|---|
| Component nhận ref bằng cách nào | Đối số thứ hai, (props, ref) | props.ref |
| Cần hàm bọc | Có | Không |
Dùng được với useImperativeHandle | Có | Có |
| Tình trạng | Vẫn hoạt động, dự kiến bị deprecate | Cách viết cho code mới |
Ref bị component con bỏ qua sẽ mãi là null
Tự việc truyền ref cho một component không làm gì cả. Component phải gắn nó vào một phần tử. Nếu không, ref.current vẫn là null:
Console hiện Forgetful ref: null và Careful ref: INPUT. Đây là lý do phổ biến nhất khiến "ref.current is null" trên component tự viết. Trước React 19, Careful cũng sẽ là null nếu không được bọc trong forwardRef, vì ref không bao giờ được trao cho hàm.
useImperativeHandle: chỉ để lộ một API nhỏ hơn
Đưa cả nút DOM cho component cha nghĩa là nó có thể làm mọi thứ với nút đó: đổi style, xóa phần tử con, đọc những giá trị bạn muốn giữ kín. useImperativeHandle cho phép bạn tự quyết định ref.current chứa gì:
Nút cuối cùng ghi ra ["focus", "clear"]: component cha nhận đúng hai phương thức đó và không gì khác, không có nút input. Thêm phương thức select() vào đối tượng và một nút gọi nó, component cha sẽ có thêm đúng một khả năng.
Component con giữ ref riêng của nó, inputRef, cho ô nhập thật, còn handle bọc ref đó lại. Đối số thứ ba là một mảng dependency, giống với mảng của useEffect: handle được tạo lại khi các giá trị đó thay đổi.
Hãy dùng cách này một cách tiết kiệm. Phần lớn những gì component cha muốn (mở, đóng, hiện lỗi) nên là props, như isOpen hay error. Phương thức mệnh lệnh dành cho những hành động không có prop tự nhiên nào: focus, cuộn, phát video, bắt đầu một animation.
Dùng cả ref bên trong component con
Đôi khi component con cần chính nút DOM đó cho việc riêng của nó, ví dụ để đo kích thước hoặc để focus sau khi có lỗi, trong khi component cha cũng giữ một ref đến nút đó. Một thuộc tính ref chỉ nhận được một giá trị, nên hãy gộp hai ref bằng một ref callback:
function AutoGrowTextarea({ ref, ...props }) {
const localRef = useRef(null);
function setRefs(node) {
localRef.current = node;
if (typeof ref === 'function') ref(node);
else if (ref) ref.current = node;
}
return <textarea ref={setRefs} {...props} />;
}
ref của component cha có thể là một đối tượng từ useRef hoặc một hàm, nên hãy xử lý cả hai trường hợp. Khi component cha chỉ cần vài hành động, useImperativeHandle ở trên là lựa chọn gọn hơn, vì component con giữ nút DOM cho riêng mình.
Truyền ref qua nhiều lớp
Ref đi qua từng component một. Nếu Form render Field, Field render TextInput, và TextInput render <input>, thì mỗi component trong số đó phải truyền ref tiếp cho component kế tiếp. Ở React 19, đó chỉ là thêm một prop cần chuyển tiếp (<TextInput ref={ref} />); với forwardRef, mọi lớp đều phải được bọc. Ở các phiên bản cũ, trải props ({...props}) không mang theo ref, vì trước React 19 ref không bao giờ thuộc về props.
Các component của design system là nơi điều này thường quan trọng: một Button, Input hay Select bọc một phần tử gốc nên chuyển tiếp ref của nó, để ứng dụng dùng nó có thể focus, đo kích thước, hoặc trao nó cho một thư viện định vị popover.
Chuyển đổi từ forwardRef
Thay đổi này mang tính máy móc: bỏ hàm bọc và đọc ref từ props.
// Before
const Button = forwardRef(function Button({ variant, ...props }, ref) {
return <button ref={ref} className={variant} {...props} />;
});
// After (React 19)
function Button({ variant, ref, ...props }) {
return <button ref={ref} className={variant} {...props} />;
}
Không cần vội. forwardRef vẫn hoạt động trong React 19, và một thư viện phải hỗ trợ React 18 thì vẫn cần giữ nó, vì React 18 không truyền ref như một prop. Class component không bị ảnh hưởng: ref trên class component vẫn trỏ đến instance của component.
TypeScript
Với forwardRef, các đối số kiểu đi theo thứ tự: kiểu của ref trước, rồi đến props:
import { forwardRef } from 'react';
type FancyInputProps = { label: string };
const FancyInput = forwardRef<HTMLInputElement, FancyInputProps>(
function FancyInput({ label }, ref) {
return <input ref={ref} aria-label={label} />;
}
);
Ở React 19, khai báo kiểu cho ref như mọi prop khác. ComponentProps<'input'> đã bao gồm nó:
import { useImperativeHandle, useRef, type ComponentProps, type Ref } from 'react';
function FancyInput(props: ComponentProps<'input'>) {
return <input {...props} />;
}
type SearchHandle = { focus: () => void; clear: () => void };
function SearchBox({ ref }: { ref?: Ref<SearchHandle> }) {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => {
if (inputRef.current) inputRef.current.value = '';
},
}));
return <input ref={inputRef} />;
}
// In the parent
const searchRef = useRef<SearchHandle>(null);
Trang useRef nói về bản thân ref: truy cập DOM, giá trị được giữ lại mà không gây render, và ref callback. Để khai báo kiểu cho component nói chung, xem React với TypeScript.
Câu hỏi thường gặp
forwardRef trong React dùng để làm gì?
Nó bọc một function component để ref do component cha truyền vào đến được component dưới dạng đối số thứ hai, (props, ref). Sau đó component gắn ref này vào một nút DOM, để component cha có thể gọi các phương thức như focus() trên nút đó.
forwardRef có bị deprecated trong React 19 không?
Chưa, và nó vẫn hoạt động. React 19 truyền ref cho function component như một prop thông thường, nên code mới không cần đến nó, và nhóm phát triển React cho biết họ dự định deprecate forwardRef trong một phiên bản sau.
Vì sao ref trên component tự viết của tôi lại là null?
Component đã nhận ref nhưng không gắn nó vào phần tử nào. Hãy gắn nó vào một nút DOM bên trong: <input ref={ref} />. Trước React 19, ref hoàn toàn không được truyền vào trừ khi component được bọc trong forwardRef.
useImperativeHandle dùng để làm gì?
Nó thay thế những gì component cha thấy trong ref.current. Thay vì cả nút DOM, bạn trả về một đối tượng chỉ chứa các phương thức bạn chọn, như focus và clear.
Class component có nhận ref như một prop trong React 19 không?
Không. Ref trên class component vẫn trỏ đến instance của component. Thay đổi này chỉ áp dụng cho function component.