El hook useRef
es una herramienta poderosa en la API de hooks de React que proporciona una forma de crear una referencia mutable que persiste a través de los renderizados. A diferencia de las variables de estado, cambiar el valor de una referencia no desencadena un nuevo renderizado, lo que lo hace ideal para ciertos casos de uso que exploraremos en esta lección.
El hook useRef
devuelve un objeto mutable con una propiedad .current
que se inicializa con el argumento proporcionado. Este objeto persiste durante toda la vida útil del componente.
1const refContainer = useRef(initialValue);
El objeto devuelto por useRef
tiene dos características importantes:
.current
Uno de los usos más comunes de useRef
es acceder y manipular elementos DOM directamente:
1import React, { useRef, useEffect } from 'react'; 2 3function AutoFocusInput() { 4 // Crear una referencia 5 const inputRef = useRef(null); 6 7 // Usar la referencia en useEffect 8 useEffect(() => { 9 // Enfocar el elemento input cuando el componente se monta 10 inputRef.current.focus(); 11 }, []); 12 13 return <input ref={inputRef} type="text" />; 14}
En este ejemplo:
useRef(null)
ref
inputRef.current
focus()
en el elemento inputOtro uso poderoso de useRef
es mantener un valor que persiste a través de los renderizados sin causar nuevos renderizados cuando cambia:
1import React, { useRef, useEffect, useState } from 'react'; 2 3function IntervalCounter() { 4 const [count, setCount] = useState(0); 5 6 // Almacenar el ID del intervalo en una referencia para que persista entre renderizados 7 const intervalIdRef = useRef(null); 8 9 useEffect(() => { 10 // Configurar el intervalo 11 intervalIdRef.current = setInterval(() => { 12 setCount(prevCount => prevCount + 1); 13 }, 1000); 14 15 // Limpiar el intervalo 16 return () => { 17 clearInterval(intervalIdRef.current); 18 }; 19 }, []); // Array de dependencias vacío significa que se ejecuta una vez al montar 20 21 return <div>Contador: {count}</div>; 22}
Aquí, usamos una referencia para almacenar el ID del intervalo para poder limpiarlo cuando el componente se desmonte. Usar una referencia es mejor que una variable de estado porque:
Puedes usar referencias para rastrear valores anteriores de props o estado:
1import React, { useRef, useEffect, useState } from 'react'; 2 3function TrackChanges({ value }) { 4 const prevValueRef = useRef(); 5 const [hasChanged, setHasChanged] = useState(false); 6 7 useEffect(() => { 8 // Comprobar si el valor ha cambiado desde el último renderizado 9 if (prevValueRef.current !== undefined && prevValueRef.current !== value) { 10 setHasChanged(true); 11 } 12 13 // Actualizar la referencia al valor actual para el próximo renderizado 14 prevValueRef.current = value; 15 }, [value]); 16 17 return ( 18 <div> 19 <p>Valor actual: {value}</p> 20 {hasChanged && <p>¡El valor ha cambiado!</p>} 21 </div> 22 ); 23}
Este patrón es útil cuando necesitas comparar valores actuales y anteriores para realizar efectos secundarios.
Es importante entender cuándo usar useRef
versus useState
:
Característica | useRef | useState |
---|---|---|
Desencadena re-renderizado cuando se actualiza | No | Sí |
Persiste entre renderizados | Sí | Sí |
Patrón de acceso | ref.current | [state, setState] |
Mejor para | Referencias DOM, almacenar valores sin re-renderizados | Estado de UI que necesita ser mostrado |
Usa useRef
cuando:
Usa useState
cuando:
1// Inicialización perezosa 2const videoRef = useRef(null); 3 4// Con valor inicial 5const countRef = useRef(0);
1// Acceso DOM básico 2const imageRef = useRef(null); 3 4// Más adelante en el código: 5if (imageRef.current) { 6 const dimensions = { 7 width: imageRef.current.clientWidth, 8 height: imageRef.current.clientHeight 9 }; 10}
1// Uso incorrecto - La UI no se actualizará cuando count cambie 2const countRef = useRef(0); 3const handleClick = () => { 4 countRef.current += 1; 5 // ¡No ocurre re-renderizado! 6}; 7 8// Uso correcto 9const [count, setCount] = useState(0); 10const handleClick = () => { 11 setCount(count + 1); 12 // El componente se volverá a renderizar 13};
1// Buena práctica 2const inputRef = useRef(null); 3 4const focusInput = () => { 5 // Comprobar si la referencia está adjunta 6 if (inputRef.current) { 7 inputRef.current.focus(); 8 } 9};
1import React, { useRef, useState } from 'react'; 2 3function VideoPlayer() { 4 const videoRef = useRef(null); 5 const [isPlaying, setIsPlaying] = useState(false); 6 7 const handlePlayPause = () => { 8 if (isPlaying) { 9 videoRef.current.pause(); 10 } else { 11 videoRef.current.play(); 12 } 13 setIsPlaying(!isPlaying); 14 }; 15 16 return ( 17 <div> 18 <video 19 ref={videoRef} 20 src="https://example.com/video.mp4" 21 /> 22 <button onClick={handlePlayPause}> 23 {isPlaying ? 'Pausar' : 'Reproducir'} 24 </button> 25 </div> 26 ); 27}
1import React, { useRef, useEffect, useState } from 'react'; 2 3function ScrollTracker() { 4 const containerRef = useRef(null); 5 const [scrollPercentage, setScrollPercentage] = useState(0); 6 7 useEffect(() => { 8 const container = containerRef.current; 9 10 const handleScroll = () => { 11 if (container) { 12 const scrollTop = container.scrollTop; 13 const scrollHeight = container.scrollHeight - container.clientHeight; 14 const percentage = (scrollTop / scrollHeight) * 100; 15 setScrollPercentage(Math.round(percentage)); 16 } 17 }; 18 19 container.addEventListener('scroll', handleScroll); 20 return () => { 21 container.removeEventListener('scroll', handleScroll); 22 }; 23 }, []); 24 25 return ( 26 <div> 27 <div 28 ref={containerRef} 29 style={{ 30 height: '300px', 31 overflow: 'auto', 32 border: '1px solid #ccc' 33 }} 34 > 35 {/* Contenido aquí */} 36 <div style={{ height: '1000px' }}> 37 Desplázate hacia abajo para ver el cambio de porcentaje 38 </div> 39 </div> 40 <p>Posición de desplazamiento: {scrollPercentage}%</p> 41 </div> 42 ); 43}
Aunque useRef
es poderoso, no siempre es la herramienta adecuada:
No lo uses para eludir el flujo de datos de React
No lo uses para valores que afectan al renderizado
No abuses del código imperativo
Para una asignación de referencia más dinámica, puedes usar una devolución de llamada en lugar del atributo ref:
1function MeasureExample() { 2 const [height, setHeight] = useState(0); 3 4 const measuredRef = useCallback(node => { 5 if (node !== null) { 6 setHeight(node.getBoundingClientRect().height); 7 } 8 }, []); 9 10 return ( 11 <> 12 <h1 ref={measuredRef}>Hola, mundo</h1> 13 <p>El encabezado anterior tiene {Math.round(height)}px de altura</p> 14 </> 15 ); 16}
Al crear abstracciones de componentes, puedes reenviar referencias a elementos internos:
1const FancyButton = React.forwardRef((props, ref) => ( 2 <button ref={ref} className="fancy-button" {...props}> 3 {props.children} 4 </button> 5)); 6 7// Uso 8function App() { 9 const buttonRef = useRef(null); 10 11 const handleClick = () => { 12 buttonRef.current.focus(); 13 }; 14 15 return ( 16 <> 17 <FancyButton ref={buttonRef}>¡Haz clic en mí!</FancyButton> 18 <button onClick={handleClick}>Enfocar el botón elegante</button> 19 </> 20 ); 21}
El hook useRef
es una herramienta versátil en el arsenal de hooks de React. Proporciona una forma de:
Al comprender cuándo y cómo usar useRef
, puedes construir componentes React más eficientes y resolver problemas que serían difíciles con solo el estado. Recuerda que aunque el código imperativo tiene su lugar, React es fundamentalmente una biblioteca declarativa, así que usa useRef
juiciosamente y preferiblemente para casos donde no existe una solución declarativa o sería excesivamente compleja.