forwardRef
forwardRef
le permite a tu componente exponer un nodo DOM al componente padre con una ref.
const SomeComponent = forwardRef(render)
Referencia
forwardRef(render)
Llama a forwardRef()
para que tu componente reciba un ref y la reenvíe a un componente hijo:
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
// ...
});
Parámetros
render
: La función de renderización de tu componente. React llama a esta función con las props yref
que tu componente recibió de su padre. El JSX que devuelve será la salida de tu componente.
Devuelve
forwardRef
devuelve un componente de React que puedes renderizar en JSX. A diferencia de los componentes de React definidos como funciones simples, un componente devuelto por forwardRef
también puede recibir una prop ref
.
Advertencias
- En el modo estricto, React llamará a tu función de renderizado dos veces para ayudarte a encontrar impurezas accidentales. Este es un comportamiento sólo de desarrollo y no ocurre en producción. Si tu función de renderizado es pura (como debería ser), esto no debería afectar a la lógica de tu componente. El resultado de una de las llamadas será ignorado.
Función render
forwardRef
acepta una función de renderizado como argumento. React llama a esta función con props
y ref
:
const MyInput = forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});
Parámetros
-
props
: Las props pasadas por el componente padre. -
ref
: El atributoref
pasado por el componente padre. Laref
puede ser un objeto o una función. Si el componente padre no ha pasado un ref, seránull
. Deberás pasar la «ref» que recibas o bien a otro componente, o bien auseImperativeHandle
.
Devuelve
forwardRef
devuelve un componente de React que puedes renderizar en JSX. A diferencia de los componentes de React definidos como funciones simples, el componente devuelto por forwardRef
puede tomar una prop ref
.
Uso
Exponer un nodo DOM al componente padre
Por defecto, los nodos DOM de cada componente son privados. Sin embargo, a veces es útil exponer un nodo DOM al padre, por ejemplo, para permitir enfocarlo. Para permitirlo, envuelve la definición de tu componente con forwardRef()
:
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} />
</label>
);
});
Recibirás una ref como segundo argumento después de props. Pásala al nodo DOM que quieras exponer:
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} ref={ref} />
</label>
);
});
Esto permite al componente padre Form
acceder al <input>
nodo DOM expuesto por MyInput
:
function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
}
return (
<form>
<MyInput label="Enter your name:" ref={ref} />
<button type="button" onClick={handleClick}>
Editar
</button>
</form>
);
}
Este componente Form
pasa una ref a MyInput
. El componente MyInput
pasa esa ref a la etiqueta <input>
del navegador. Como resultado, el componente Form
puede acceder a ese nodo DOM <input>
y llamar a focus()
en él.
Ten en cuenta que al exponer una ref al nodo DOM dentro de tu componente, estás dificultando la posibilidad de cambiar el interior de tu componente más adelante. Por lo general, expondrás los nodos DOM de los componentes reutilizables de bajo nivel como los botones o las entradas de texto, pero no lo harás para los componentes de nivel de aplicación como un avatar o un comentario.
Ejemplo 1 de 2: Enfocar una entrada de texto
Al hacer clic en el botón el campo de texto (input) tomará el foco. El componente Form
define una ref y la pasa al componente MyInput
. El componente MyInput
la reenvía al elemento nativo <input>
. Esto permite que el componente Form
enfoque el <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="Enter your name:" ref={ref} /> <button type="button" onClick={handleClick}> Editar </button> </form> ); }
Pasar una ref a través de múltiples componentes
En lugar de pasar una ref
a un nodo DOM, puedes pasarla a un componente propio como MyInput
.:
const FormField = forwardRef(function FormField(props, ref) {
// ...
return (
<>
<MyInput ref={ref} />
...
</>
);
});
Si ese componente MyInput
pasa una ref a su <input>
, una ref a FormField
te dará ese <input>
:
function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
}
return (
<form>
<FormField label="Enter your name:" ref={ref} isRequired={true} />
<button type="button" onClick={handleClick}>
Editar
</button>
</form>
);
}
El componente Form
del formulario define una ref y la pasa a FormField
. El componente FormField
pasa esa ref a MyInput
, que a su vez la pasa a un nodo DOM <input>
. Así es como Form
accede a ese nodo 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="Enter your name:" ref={ref} isRequired={true} /> <button type="button" onClick={handleClick}> Editar </button> </form> ); }
Exposición de un manejador imperativo en lugar de un nodo DOM
En lugar de exponer un nodo DOM completo, puedes exponer un objeto personalizado, llamado manejador imperativo (imperative handle), con un conjunto de métodos más restringido. Para hacer esto, tendrías que definir una ref separada para guardar el nodo DOM:
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
// ...
return <input {...props} ref={inputRef} />;
});
A continuación, pasa la ref
que has recibido a useImperativeHandle
y especifica el valor que quieres exponer a la 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 algún componente obtiene ahora una ref a MyInput
, sólo recibirá su objeto { focus, scrollIntoView }
en lugar del nodo DOM. Esto te permite limitar la información que expones sobre tu nodo DOM al mínimo.
import { useRef } from 'react'; import MyInput from './MyInput.js'; export default function Form() { const ref = useRef(null); function handleClick() { ref.current.focus(); // This won't work because the DOM node isn't exposed: // ref.current.style.opacity = 0.5; } return ( <form> <MyInput placeholder="Enter your name" ref={ref} /> <button type="button" onClick={handleClick}> Editar </button> </form> ); }
Más información sobre el uso de manejadores imperativos.
Solución de problemas
Mi componente está envuelto en forwardRef
, pero la ref
a él es siempre null
.
Esto suele significar que olvidaste utilizar la ref
que recibiste.
Por ejemplo, este componente no hace nada con su ref
:
const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input />
</label>
);
});
Para solucionarlo, pasa la ref
a un nodo DOM o a otro componente que pueda aceptar una ref:
const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input ref={ref} />
</label>
);
});
La ref
a MyInput
también podría ser null
si parte de la lógica es condicional:
const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
{showInput && <input ref={ref} />}
</label>
);
});
Si showInput
es false
, la ref no será reenviada a ningún nodo, y una ref a MyInput
permanecerá vacía. Esto es particularmente fácil de pasar por alto si la condición está oculta dentro de otro componente, como Panel
en este ejemplo:
const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
<Panel isExpanded={showInput}>
<input ref={ref} />
</Panel>
</label>
);
});