Estuvo genial el reto, aprendí mucho de useEffect
leyendo la documentación oficial de cómo remover las dependencias de useEffect
buscando una solución al bug del segundo reto.
.
En resumen, aprendí que el linter siempre estará pendiente de que agreguemos en el array de dependencias de useEffect
todos los valores reactivos que usemos dentro del mismo, como props o estados. Como onLazyLoad
es una función dentro de los props del componente LazyImage
, React lo trata como un valor reactivo. Sin embargo, no queremos agregar esta función en las dependencias de useEffect
, porque lo único que nos interesa de onLazyLoad
es obtener sus datos, más no ejecutar el callback de useEffect
cada vez que onLazyLoad
cambie.
.
La mejor solución en la documentación es utilizar el hook (en fase experimental a la fecha de escribir este comentario 15/03/23) useEffectEvent
. Este hook le indica a useEffect
que vamos a utilizar un valor reactivo dentro del mismo, pero no queremos que ‘reaccione’ a sus cambios, sino que solamente lo use para lectura. Este hook al estar en fase experimental, preferí no usarlo pero está bastante interesante para cuando salga.
.
Al final, opté por la opción que React no recomienda, que es desactivar el linter. Como en todo, cada cosa a su momento, y en este caso desactivar el linter es la opción que mejor se adapta a mi código porque solamente quiero leer lo que me arroja onLazyLoad
, más no reaccionar a sus cambios. Es de esos momentos cuando la solución menos recomendada resulta ser la más óptima para un caso específica, o por lo menos la más sencilla según yo
interface LazyImageProps extends ImgHTMLAttributes<HTMLImageElement> {
src: string
alt: string,
// especificamos que onLazyLoad es un valor opcional de tipo void que aceptará un argumento de tipo HTMLImageElement
onLazyLoad?: (node: HTMLImageElement) => void
}
export default function LazyImage({src, alt, onLazyLoad, ...imgOptionalAttrs}: LazyImageProps) {
const node = useRef<HTMLImageElement>(null)
const [currentSrc, setCurrentSrc] = useState(defaultImg)
useEffect(() => {
const intersectionObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
setCurrentSrc(src)
// ya que onLazyLoad es un valor opcional, validamos que sea verdadero y lo ejecutamos pasando el target de la entrada como argumento el cual contiene todos los datos del elemento img
onLazyLoad && onLazyLoad(entry.target as HTMLImageElement)
// le digo al intersectionObserver que deje de observar a img luego de entrar al viewport y ejecutar el código de arriba
intersectionObserver.unobserve(entry.target)
}
})
})
node.current && intersectionObserver.observe(node.current)
return () => { intersectionObserver.disconnect() }
// DANGER ZONE: le digo al linter que ignore la línea de abajo
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [src])
return (
<img ref={node} src={currentSrc} alt={alt} {...imgOptionalAttrs} />
)
}
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?