Héctor BlisS

@blissito

hace 2 años

¿Y cómo declaro los tipos para un useRef?

Has comenzado valientemente a implementar TS en tu código, ya no creas archivos .jsx ahora son .tsx, te sientes algo inseguro pero entusiasmado a la vez y ¡todo va bien! tus componentes y la declaración de tus props se ven muy pro y la linterna te ayuda en tu día a día como developer, incluso si eres suficientemente observador(a) ya notaste que ya no pasas tantas horas buscando un error desconocido y que logras más en menos tiempo. Sip Typescript está chingón. Hasta que... (música de "Stranger Things" y suspenso)

Resulta que te aparecen errores cuando usas algunos hooks que requieren unos tipos muy específicos como por ejemplo cuando utilizas forwardRef para pasar un ref como prop para usar la api de un nodo HTML; cómo cuándo quieres que el sitio web haga scroll hasta cierto elemento en la página.

useRef ts error

¿Cómo declaramos correctamente los tipos para useRef?

En esta entrada voy a compartirte cómo declaro mis tipos cuando trabajo con useRef y forwardRef, con 3 sencillas reglas

Regla número 1. HTMLElement

Siempre es bueno ser muy específico con los nodos que se almacenarán en una referencia, mientras más acotadas sean las opciones es mejor, así evitamos que nuestro programa tenga demasiadas opciones para fallar, en mi ejemplo voy a imaginar que tengo un par de secciones en mi página y cuando selecciono una de esas secciones en un menú, el sitio web hace scroll hasta llegar a dicha sección. Simple. Con eso en mente declaremos nuestro componente contenedor:

1const Container = () => { 2 const nosotrosRef = useRef<HTMLDivElement>() 3 const preciosRef = useRef<HTMLDivElement>() 4 const contactoRef = useRef<HTMLDivElement>() 5 return ( 6 <div> 7 <Section id="nosotros" ref={nosotrosRef} /> 8 <Section id="precios" ref={preciosRef} /> 9 <Section id="contacto" ref={contactoRef} /> 10 </div> 11 ) 12} 13

En este caso estamos seguros que la referencia contendrá un nodo HTMLDivElement pero dependiendo de tu app podría ser cualquier nodo o incluso un grupo de ellos <HTMLDivElement | HTMLFormElement> Incluso si aún no estás segura de cual usarás puedes quedarte un nivel arriba HTMLElement para después actualizar.

HTMLElements

Aquí también por buena práctica vamos a inicializar nuestra referencia con null lo que nos ahorrará algunos otros errores que describiré en el siguiente punto y que no queremos ver.

1 const nosotrosRef = useRef<HTMLDivElement>(null) 2 const preciosRef = useRef<HTMLDivElement>(null) 3 const contactoRef = useRef<HTMLDivElement>(null) 4

Regla número 2. RefObject

Donde sea que veas este warning, donde el protagonista es nuestro viejo conocido undefined es necesario deshacernos de él, porque si no, nos querrá presentar a su compa el unknown.

Type mutableRefObject<unknow>

Para resolver esto sólo tenemos que declarar nuestra referencia en nuestro componente Section con el mismo tipo de dato que se le ha pasado desde el padre, y para ello tenemos una herramienta de React que se llama: RefObject y podemos usarla así:

1const Section = forwardRef(( 2{ id }: { id: string }, 3ref:RefObject<HTMLDivElement> 4) => ( 5 <div ref={ref}> 6 <h2>Sección {id}</h2> 7 </div> 8)); 9

Aquí estamos diciendo que ref recibirá un objecto ref ({current:Node}) con un nodo div dentro de current.

Gracias React team.

Regla número 3. read only vs read&write

Una referencia por naturaleza está pensada para guardar un valor que no cambia con el tiempo a través de diferentes renders, y que en teoría debería almacenar un valor constante, pero su uso se ha extendido y ahora lo usamos también para guardar datos dinámicos pero que no necesitan detonar renders del componente, y es importante decirle a Typescript que mutaremos el valor de este ref aunque la manera de informarselo no es obvia...

Vamos a decir que por alguna razón muy creativa nuestra referencia va a cambiar de almacenar un div a estar vacía (o darle algún otro nodo como un form o un h2), dependiendo de la acción del usuario.

En la función que actualiza la referencia tendremos un error así:

current is a read-only

La forma más moderna de resolver este error es declarar los tipos del forwardRef y tolerar el null así que cambiamos la forma de declarar nuestros tipos de la siguiente manera:

1type SectionProps = { 2 id: string; 3}; 4 5const Section = 6forwardRef<HTMLDivElement | null, SectionProps> 7(({ id }, ref) => { 8 const toggle = () => { 9 ref.current = null; 10}; 11 return ( 12 <div ref={ref}> 13 <h2>Sección {id}</h2> 14 </div> 15 ); 16}); 17

De esta forma toleramos no sólo que ref pueda mutar a null (o algún otro tipo) también toleramos la mutación en sí misma, es decir la escritura en ref.current

Es importante decir que el orden de la declaración en el forwardRef es importante, primero declara el tipo del ref y luego el de los props.

Conclusión

¡Y ya está! considera estas 3 simples reglas cada que declares referencias y no olvides darte una revisada a la lista de tipos HTML que existen para que no te sorprenda useRef nunca más, te dejo unos links interesantes aquí abajo.

Si quieres aprender a programar, también puedes ir directo a mi curso gratis en fixtergeek.com o suscríbete a mi lista de correo para que no te pierdas mi contenido sobre TS. Además de que estoy por lanzar un curso de Typescript para principiantes en las próximas semanas, ¡entérate del lanzamiento suscribiéndote!

Sin más, gracias por tu tiempo. Te mando un abrazo.

Happy coding <3

Bliss.

Recursos extra:

Lista interminable de tipos:

Forward ref

Aprende a programar con JavaScript

Suscríbete a mi lista VIP

Y no te pierdas las actualizaciones

No te enviaré spam. Desuscríbete en cualquier momento.