forwardRef
forwardRef
permite a su componente exponer un nodo DOM al componente padre con un ref.
const SomeComponent = forwardRef(render)
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 su enfoque. Para optar por ello, envuelve la definición de su componente en forwardRef()
:
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} />
</label>
);
});
Recibirás un ref como segundo argumento después de props. Pásalo 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}>
Edit
</button>
</form>
);
}
Éste componente Form
pasa un ref a MyInput
. El componente MyInput
envía esa referencia al tag <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 referencia 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 se centrará la entrada. El componente Form
define una referencia y la pasa al componente MyInput
. El componente MyInput
reenvía esa referencia al navegador <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}> Edit </button> </form> ); }
Reenvío de una referencia a través de múltiples componentes
En lugar de reenviar un ref
a un nodo DOM, puedes reenviarlo a tu propio componente como MyInput
.:
const FormField = forwardRef(function FormField(props, ref) {
// ...
return (
<>
<MyInput ref={ref} />
...
</>
);
});
Si ese componente MyInput
reenvía un ref a su <input>
, un 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}>
Edit
</button>
</form>
);
}
El componente Form
del formulario define una referencia y la pasa a FormField
. El componente FormField
reenvía esa referencia a MyInput
, que reenvía esta referencia a un nodo DOM del navegador <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}> Edit </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 imperative handle, con un conjunto de métodos más restringido. Para hacer esto, tendrías que definir un ref separado para mantener 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 un ref a MyInput
, sólo recibirá su objeto { focus, scrollIntoView }
en lugar del nodo DOM. Esto le permite limitar la información que expone sobre su 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 label="Enter your name:" ref={ref} /> <button type="button" onClick={handleClick}> Edit </button> </form> ); }
Más información sobre el uso de asas imperativas.
Referencias
forwardRef(render)
Llama a forwardRef()
para que su componente reciba un ref y lo reenvíe a un componente hijo:
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
// ...
});
Parametros
render
: La función de renderización de tu componente. React llama a esta función con los props yref
que tu componente recibió de su padre. El JSX que devuelve será la salida de tu componente.
Returns
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 proposición ref
.
Advertencias
- En el modo estricto, React llamará a su función de renderizado dos veces para ayudarte a encontrar impurezas accidentales. Este es un comportamiento sólo de desarrollo y no afecta a la 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>
);
});
Parametros
-
props
: Los accesorios pasados por el componente padre. -
ref
: El atributoref
pasado por el componente padre. Elref
puede ser un objeto o una función. Si el componente padre no ha pasado un ref, seránull
. Deberá pasar el “ref” que reciba a otro componente, o pasarlo auseImperativeHandle
.
Returns
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 proposición ref
.
Solución de problemas
Mi componente está envuelto en forwardRef
, pero el ref
a él es siempre null
.
Esto suele significar que se ha olvidado de utilizar el ref
que ha recibido.
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 el ref
a un nodo DOM o a otro componente que pueda aceptar un ref:
const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input ref={ref} />
</label>
);
});
El 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
, el ref no será reenviado a ningún nodo, y un ref a MyInput
permanecerá vacío. 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>
);
});