forwardRef
forwardRef
permet à votre composant d’exposer un nœud DOM à son composant parent au travers d’une ref.
const SomeComponent = forwardRef(render)
Référence
forwardRef(render)
Appelez forwardRef()
pour que votre composant reçoive une ref qu’il puisse transmette à un de ses composants enfants :
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
// ...
});
Voir dautres exemples ci-dessous.
Paramètres
render
: la fonction de rendu de votre composant. React appellera cette fonction avec les props et laref
que votre composant aura reçu de son parent. Cette fonction renvoie, comme d’habitude, le JSX constitutif du composant.
Valeur renvoyée
forwardRef
renvoie un composant React qui peut figurer dans un rendu JSX. Contrairement aux composants React définis par des fonctions classiques, un composant renvoyé par forwardRef
pourra en prime accepter une prop ref
.
Limitations
- En mode strict, React appellera votre fonction composant deux fois afin de vous aider à repérer des impuretés accidentelles. Ce comportement est limité au développement et n’affecte pas la production. Une des valeurs renvoyées sera ignorée. Si votre fonction composant est pure (ce qui devrait être le cas), ça n’affectera en rien son comportement.
La fonction render
forwardRef
accepte une fonction de rendu en argument. React appellera cette fonction avec props
et ref
:
const MyInput = forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});
Paramètres
-
props
: les props passées par le composant parent. -
ref
: la propref
passée par le composant parent. Laref
peut être un objet ou une fonction. Si le composant parent n’a pas passé de ref, elle seranull
. Vous pouvez soit passer laref
reçue à un autre composant soit la passer àuseImperativeHandle
.
Valeur renvoyée
forwardRef
renvoie un composant React qui peut figurer dans un rendu JSX. Contrairement aux composants React définis par des fonctions classiques, un composant renvoyé par forwardRef
pourra en prime accepter une prop ref
.
Utilisation
Exposer un nœud DOM au composant parent
Par défaut, tous les nœuds DOM de votre composant sont privés. Ceci dit, il peut parfois être utile d’exposer un nœud DOM à votre parent — par exemple pour en permettre l’activation. Pour permettre ça, enrobez votre définition de composant avec forwardRef()
:
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} />
</label>
);
});
Vous recevrez une ref comme second argument, juste après les props. Passez-la au nœud DOM que vous souhaitez exposer :
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} ref={ref} />
</label>
);
});
Ça permet au composant parent Form
d’accéder au nœud DOM <input>
exposé par MyInput
:
function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
}
return (
<form>
<MyInput label="Saisissez votre nom :" ref={ref} />
<button type="button" onClick={handleClick}>
Modifier
</button>
</form>
);
}
Le composant Form
passe une ref à MyInput
. Le composant MyInput
transmet cette ref à la balise native <input>
. Résultat, le composant Form
peut accéder au nœud DOM <input>
et appeler sa méthode focus()
.
Gardez à l’esprit qu’exposer une ref vers un nœud DOM au sein de votre composant peut vous embêter plus tard si vous souhaitez refondre la structure interne de celui-ci. Classiquement, vous exposerez des nœuds DOM depuis des composants réutilisables de bas niveau tels que des boutons ou des champs de saisie, mais vous éviterez de le faire pour des composants applicatifs comme un avatar ou un bloc de commentaire.
Exemple 1 sur 2 · Activer un champ de saisie
Un clic sur le bouton activera le champ de saisie. Le composant Form
définit une ref qu’il passe au composant MyInput
. Ce composant MyInput
transmet la ref au <input>
du navigateur. Ça permet au composant Form
d’activer le <input>
.
import { useRef } from 'react'; import MyInput from './MyInput.js'; export default function Form() { const ref = useRef(null); function handleClick() { ref.current.focus(); } return ( <form> <MyInput label="Saisissez votre nom :" ref={ref} /> <button type="button" onClick={handleClick}> Modifier </button> </form> ); }
Transmettre une ref à travers plusieurs composants
Au lieu de transmettre la ref
à un nœud DOM, vous avez parfois besoin de la transmettre à votre propre composant, comme MyInput
:
const FormField = forwardRef(function FormField(props, ref) {
// ...
return (
<>
<MyInput ref={ref} />
...
</>
);
});
Si ce composant MyInput
transmet une ref à son <input>
, une ref à FormField
vous donnera ce <input>
:
function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
}
return (
<form>
<FormField label="Saisissez votre nom :" ref={ref} isRequired={true} />
<button type="button" onClick={handleClick}>
Modifier
</button>
</form>
);
}
Le composant Form
définit une ref et la passer au FormField
. Le composant FormField
transmet cette ref au MyInput
, qui la transmet au nœud DOM <input>
. C’est ainsi que Form
accède à ce nœud DOM.
import { useRef } from 'react'; import FormField from './FormField.js'; export default function Form() { const ref = useRef(null); function handleClick() { ref.current.focus(); } return ( <form> <FormField label="Saisissez votre nom :" ref={ref} isRequired={true} /> <button type="button" onClick={handleClick}> Modifier </button> </form> ); }
Exposer un point d’accès impératif plutôt qu’un nœud DOM
Au lieu d’exposer l’intégralité du nœud DOM, vous pouvez exposer un objet personnalisé qu’on appelle point d’accès impératif, doté d’un jeu plus restreint de méthodes. Pour cela, vous devez définir une ref séparée pour référencer le nœud DOM :
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
// ...
return <input {...props} ref={inputRef} />;
});
Passez la ref
que vous avez reçue à useImperativeHandle
et spécifiez la valeur que vous souhaitez exposer en tant que ref
:
import { forwardRef, useRef, useImperativeHandle } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
};
}, []);
return <input {...props} ref={inputRef} />;
});
Si un composant récupère la ref de MyInput
, il ne recevra que votre objet { focus, scrollIntoView }
au lieu du nœud DOM. Ça vous permet de limiter au minimum les parties du nœud DOM que vous exposez.
import { useRef } from 'react'; import MyInput from './MyInput.js'; export default function Form() { const ref = useRef(null); function handleClick() { ref.current.focus(); // Ça ne marchera pas parce que le nœud DOM // n'est pas exposé : // ref.current.style.opacity = 0.5; } return ( <form> <MyInput placeholder="Saisissez votre nom :" ref={ref} /> <button type="button" onClick={handleClick}> Modifier </button> </form> ); }
En savoir plus sur les points d’accès impératifs.
Dépannage
Mon composant est enrobé par forwardRef
, mais la ref
que je reçois est toujours null
Ça signifie généralement que vous avez oublié d’utiliser effectivement la ref que vous avez reçue.
Par exemple, ce composant ne fait rien avec sa ref
:
const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input />
</label>
);
});
Pour corriger ça, transmettez la ref
au nœud DOM ou à un autre composant qui peut acccepter une ref :
const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input ref={ref} />
</label>
);
});
La ref
à MyInput
pourrait aussi être null
si une partie de la logique était conditionnelle :
const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
{showInput && <input ref={ref} />}
</label>
);
});
Si showInput
est false
, alors la ref ne sera transmise à aucun nœud, et la ref à MyInput
restera vide. C’est particulièrement difficile à repérer si la condition est enfouie dans un autre composant, comme Panel
dans l’exemple ci-après :
const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
<Panel isExpanded={showInput}>
<input ref={ref} />
</Panel>
</label>
);
});