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.

Prueba algunos ejemplos

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.

Atención

No abuses de los refs. Sólo deberías usar refs para comportamientos imperativos que no puedes expresar como props: por ejemplo, desplazarse a un nodo, enfocar un nodo, disparar una animación, seleccionar texto, etc.

Si puedes expresar algo como un prop, no debes usar un ref. Por ejemplo, en lugar de exponer un manejador imperativo como { open, close } de un componente Modal, es mejor tomar isOpen como prop <Modal isOpen={isOpen} />. Effects puede ayudarte a exponer comportamientos imperativos a través de props.


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 y ref 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 atributo ref pasado por el componente padre. El ref 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 a useImperativeHandle.

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>
);
});