forwardRef permette a un componente genitore di passare un ref attraverso il tuo componente fino a un elemento DOM al suo interno, così il genitore può chiamare focus(), scrollIntoView() e metodi simili su quell'elemento. In React 19 non ti serve più: i function component ricevono ref come una prop qualsiasi. forwardRef funziona ancora, e lo vedrai nella maggior parte del codice esistente, quindi questa pagina mostra entrambi.
forwardRef riceve una funzione di rendering con due argomenti: props e il ref passato dal genitore. ref non è dentro props; arriva da solo. Clicca Focus e la console conferma che inputRef.current è il vero nodo <input>.
Il modo di React 19: ref è una prop
Da React 19 un function component riceve ref nelle sue props come qualsiasi altra prop. Nessun wrapper, nessun secondo argomento:
Scrivi qualcosa e clicca Log value: il genitore legge il testo attuale dell'input direttamente dal nodo DOM. Elimina ref={ref} dall'<input> e clicca Focus: la console mostra un errore, perché ora inputRef.current è null.
forwardRef (tutte le versioni) | prop ref (React 19+) | |
|---|---|---|
| Come il componente riceve il ref | Secondo argomento, (props, ref) | props.ref |
| Serve un wrapper | Sì | No |
Funziona con useImperativeHandle | Sì | Sì |
| Stato | Funziona, deprecazione prevista | Il modo di scrivere il codice nuovo |
Un ref ignorato dal figlio resta null
Passare ref a un componente non fa nulla da solo. Il componente deve metterlo su un elemento. Se non lo fa, ref.current resta null:
La console mostra Forgetful ref: null e Careful ref: INPUT. È il motivo più comune di "ref.current è null" su un componente personalizzato. Prima di React 19, anche Careful sarebbe stato null a meno di essere racchiuso in forwardRef, perché il ref non veniva mai consegnato alla funzione.
useImperativeHandle: esporre un'API più piccola
Dare al genitore l'intero nodo DOM significa che può farci qualsiasi cosa: cambiare stili, rimuovere figli, leggere valori che volevi tenere privati. useImperativeHandle ti permette invece di decidere cosa contiene ref.current:
L'ultimo pulsante registra ["focus", "clear"]: il genitore riceve quei due metodi e nient'altro, non il nodo dell'input. Aggiungi un metodo select() all'oggetto e un pulsante che lo chiama, e il genitore ottiene esattamente una capacità in più.
Il figlio tiene il proprio ref, inputRef, per l'input reale, e l'handle lo racchiude. Il terzo argomento è un array delle dipendenze, come quello di useEffect: l'handle viene ricostruito quando quei valori cambiano.
Usalo con parsimonia. La maggior parte delle cose che un genitore vuole (aprire, chiudere, mostrare un errore) funziona meglio come props, come isOpen o error. I metodi imperativi servono per azioni che non hanno una prop naturale: dare il focus, scorrere, riprodurre un video, avviare un'animazione.
Usare il ref anche dentro il figlio
A volte il figlio ha bisogno dello stesso nodo DOM per il proprio lavoro, per esempio per misurarlo o per dargli il focus dopo un errore, mentre anche il genitore ne tiene un ref. Un attributo ref può ricevere un solo valore, quindi unisci i due con un 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} />;
}
Il ref del genitore può essere un oggetto di useRef o una funzione, quindi gestisci entrambi i casi. Quando al genitore servono solo alcune azioni, useImperativeHandle qui sopra è la scelta più pulita, perché il figlio tiene il nodo per sé.
Passare un ref attraverso più livelli
Un ref viaggia un componente alla volta. Se Form renderizza Field, che renderizza TextInput, che renderizza l'<input>, ognuno di quei componenti deve passare ref al successivo. In React 19 è una prop in più da inoltrare (<TextInput ref={ref} />); con forwardRef, ogni livello doveva essere racchiuso. Lo spread delle props ({...props}) non lo trasporta nelle versioni più vecchie, perché prima di React 19 ref non faceva mai parte di props.
I componenti di un design system sono il caso tipico in cui questo conta: un Button, Input o Select che racchiude un elemento nativo dovrebbe inoltrare il suo ref, così l'app che lo usa può dargli il focus, misurarlo o consegnarlo a una libreria che posiziona i popover.
Migrare da forwardRef
La modifica è meccanica: togli il wrapper e leggi ref dalle 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} />;
}
Non c'è fretta. forwardRef continua a funzionare in React 19, e una libreria che deve supportare React 18 deve mantenerlo, perché React 18 non passa ref come prop. I class component non sono interessati: un ref su un class component punta ancora all'istanza del componente.
TypeScript
Con forwardRef, gli argomenti di tipo arrivano nell'ordine: tipo del ref, poi 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} />;
}
);
In React 19, tipizza ref come qualsiasi altra prop. ComponentProps<'input'> lo include già:
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);
La pagina su useRef tratta i ref in sé: accesso al DOM, valori che persistono senza rendering e ref callback. Per tipizzare i componenti in generale, vedi React con TypeScript.
Domande frequenti
Cosa fa forwardRef in React?
Racchiude un function component in modo che un ref dato dal genitore raggiunga il componente come secondo argomento, (props, ref). Il componente mette poi quel ref su un nodo DOM, così il genitore può chiamarci metodi come focus().
forwardRef è deprecato in React 19?
Non ancora, e funziona ancora. React 19 passa ref ai function component come prop normale, quindi il codice nuovo non ne ha bisogno, e il team di React ha detto di voler deprecare forwardRef in una versione futura.
Perché il mio ref è null su un componente personalizzato?
Il componente ha ricevuto il ref ma non l'ha messo su alcun elemento. Collegalo a un nodo DOM al suo interno: <input ref={ref} />. Prima di React 19 il ref non veniva passato affatto a meno che il componente non fosse racchiuso in forwardRef.
A cosa serve useImperativeHandle?
Sostituisce ciò che il genitore vede in ref.current. Invece dell'intero nodo DOM restituisci un oggetto con solo i metodi che scegli, come focus e clear.
In React 19 i class component ricevono ref come prop?
No. Un ref su un class component punta ancora all'istanza del componente. Il cambiamento riguarda solo i function component.