No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

No se trata de lo que quieres comprar, sino de quién quieres ser. Invierte en tu educación con el precio especial

Antes: $249

Currency
$209

Paga en 4 cuotas sin intereses

Paga en 4 cuotas sin intereses
Suscríbete

Termina en:

11 Días
22 Hrs
58 Min
23 Seg

Reto: sigamos extendiendo el DOM

12/16
Recursos

Aportes 11

Preguntas 3

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

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

Bueno no entendí el reto my bien, lo unico que pude enteder es que tipo de dato era la funcion onLazyLoad la cual logré, más allá de eso no entendí mucho los retos. Aquí dejo la primera parte del primer reto, OJO, NO ESTÁ COMPLETO

Código

type LazyImageProps = {
  src: string;
  onLazyLoad?: ()=> void 
};

Mi solucion

interface Props extends ImgHTMLAttributes<HTMLImageElement> {
    src: string,
    onLazyLoad?: (node: HTMLImageElement) => void
}
useEffect(() => {
        const observer = new IntersectionObserver((entries) => {
            entries.forEach((entry) => {
                if(entry.isIntersecting) {
                    console.log("Hey")
                    setCurrentSrc(src)

                    if(node.current) {
                        onLazyLoad && onLazyLoad(node.current)
                        observer.disconnect()
                    }
                }
            })
        })

        if(node.current) {
            observer.observe(node.current)
        }

        return () => {
            observer.disconnect()
        }
}, [src, onLazyLoad])

Yo lo solucione un poco diferente sin pasar el nodo y usando prevState

Comparto mi solución al reto:

// LazyLoad.tsx

  1. definir y typar onLazyLoad como una función opcional y que recibe un parámetro que es un elemento HTMLImageElement o null y que retorna vacío. (Se añade el tipo null ya que la referencia de la imagen con useRef inicia con un valor inicial de null).

  2. implementar el uso de la nueva prop onLazyLoad. Al ser una propiedad opcional, typescript se quejará si queremos utilizar sin antes validar que realmente sea una función y no un valor undefined. Por lo que se añade una validación

// page.tsx

  1. Definimos la función onLazyLoad y para tiparla, en mi caso decidí reutilizar el tipo que ya definimos en el componente LazyLoad.tsx, por lo que solo lo importo y podemos typar la función con el typo que acabamos de definir y tendremos todas las ventajas que typescript da.

Hay un detalle y es que si en el componente LazyLoad.tsx utilizamos la función onLazyLoad dentro del useEffect. Habrá que añadirla en el arreglo de dependencias, ya que es un valor externo que podría cambiar.
Si hacemos esto en nuestro ejemplo la aplicación funciona correctamente. pero en realidad dentro el useEffect se estará ejecutando cada que haya un rerender del componente (en nuestro caso, cada que añadimos una imagen), ya que la referencia de onLazyLoad cambia en cada rerender. Esto podría ocasionar comportamientos inesperados o loops infinitos dependiendo de si llegásemos a mutar ese useEffect.

Una solución a eso sería que utilizáramos el hook useCallback al definir la función en el page.tsx. Pero esa no sería una buena implementación ya que estaríamos obligando al desarrollador a usar un useCallback cada que quiera utilizar el prop onLazyLoad.

La solución que yo implemente para eso fue usar el hook useRef dentro el LazyImage.tsx para almacenar una referencia de la funcion onLazyLoad. y utilizar esa referencia en la implemetanción dentro del useEffect. como las referencias con useRef no cambian y su valor es persistente en cada rerender. No será necesario listar la funcion en el arreglo de dependencias del useEffect y sería una implementación bonita 😃

Esto último es algo vago, pero nunca esta de más saber a profundidad como funciona React

```js return ( <main > <h1 className="text-3xl font-bold underline">Hey Platzi 😎! <button onClick={addNewFox}>Add Fox</button> {images.map( ({id, url}, index) => (
{/* <h6>{id}</h6> */} <LazyImage // image={url} src={url} onClick={ () => console.log("Hey baby")} title="RandomFox" width={320} height="auto" alt="" className='rounded bg-gray-300' onLazyLoad={(img) => { console.log(`Image #${index+1} cargaba el nodo`, img) }} />
) )} </main> ) ```       ```js export const LazyImage = ({src, onLazyLoad, ...ImgProps}:Props):JSX.Element => { // Uso de props con destructing //Asignar null aqui es muy importante tenerlo en cuenta const node = useRef<HTMLImageElement>(null) const [loaded, setLoaded] = useState(false); const [currentSrc, setSrc] = useState("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIwIiBoZWlnaHQ9IjMyMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiLz4="); useEffect(() => { if (loaded) { return } // Nuevo observador const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting){ setSrc(src) if (typeof onLazyLoad === 'function' && node.current){ onLazyLoad(node.current); observer.disconnect(); setLoaded(true); } } }) }); // Observar nodo if (node.current){ observer.observe(node.current); } // Desconectarnos return () => { observer.disconnect() } }, [src, onLazyLoad]) // node.current.src // const image: string = return } ``` Esto si que me ayudo mucho.

Sin duda estos Types de html deberían venir más amigables
La tecnología va cambiando, nos vemos en unos años

Ok, aqui les voy a dejar unos snippets de código con mi solución a los dos retos:

Como se usa:

<div>
  {foxes.map(fox => 
    <LazyImage
      key={fox.id}
      onLazyLoad={imgElement => imgElement.src = fox.url}
      className='mb-5'
      alt="fox"
      width={400}
    />
  )}
</div>

Como esta implementado:

if (entry.isIntersecting) {
  if (imageRef.current && imageRef.current.src === imagePlaceholder) {
    onLazyLoad(imageRef.current)
    observer.unobserve(imageRef.current as Element)
  };
}

El imagePlaceholder es la imagen sin nada que nos paso el paso clases pasadas

Les dejo la solución que implementé, tomé varias ideas que vi en comentarios, fueron de muchísima ayuda, me gustaron mucho estos dos retos!

LazyImage.tsx


import { useState, useEffect, useRef } from "react";
import { ImgHTMLAttributes } from "react";
type NativeType = ImgHTMLAttributes<HTMLImageElement>;
type LazyImageProps = {
  alt: string;
  key: string;
  onLazyLoad?: (node: HTMLImageElement) => void;
};
type Props = LazyImageProps & NativeType;
const LazyImage = ({ src, alt, onLazyLoad, ...props }: Props): JSX.Element => {
  const imageRef = useRef<HTMLImageElement>(null);
  const [currentSource, setCurrentSource] = useState<string | undefined>("");
  useEffect(() => {
    const observer = new window.IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          setCurrentSource(src);
          if (onLazyLoad !== undefined) {
            onLazyLoad(imageRef.current!);
            observer.unobserve(imageRef.current!);
            return;
          }
        }
      });
    });
    if (imageRef.current) {
      observer.observe(imageRef.current);
    }
    return () => observer.disconnect();
  }, [src]);
index.tsx

const handleLazyLoad = (node: any): void => {
    console.log(node);
  };

Comparto mi código

Index

import { LazyImg } from "@/components/LazyImage/LazyImage";
import { useState } from "react";
import type { MouseEventHandler } from "react";
type ImageItem = { id: string; url: string };

export default function Home() {
    const [images, setImages] = useState<Array<ImageItem>>([]);

    const random = () => Math.floor(Math.random() * 123) + 1;
    const generateId = () => Math.random().toString(36).substring(2, 10);

    const handleNew: MouseEventHandler<HTMLButtonElement> = (e) => {
        e.preventDefault();
        const newFox = {
            id: generateId(),
            url: `https://randomfox.ca/images/${random()}.jpg`,
        };
        setImages([...images, newFox]);
    };

    const handleClick = () => {
        console.log("Nice fox huh?");
    };

    const handleLoaded = (node: HTMLImageElement): void => {
        console.log("Cargada" + node);
    };

    return (
        <div>
            <header className="titleFox">
                <h1 className="text-3xl font-bold">Wanna see some foxes?</h1>
                <button onClick={handleNew}>Add new</button>
            </header>
            <main className="gridFoxes">
                {images.map((i) => (
                    <div className="p-4" key={i.id}>
                        <LazyImg
                            src={i.url}
                            onLazyLoad={handleLoaded}
                            className="rounded bg-gray-300 w-80 h-auto"
                            onClick={handleClick}
                        />
                    </div>
                ))}
            </main>
        </div>
    );
}

LazyImage

import { useEffect, useRef, useState } from "react";
import type { ImgHTMLAttributes } from "react";

type LazyImageProps = {
    src: string;
    onLazyLoad: (node: HTMLImageElement) => void;
};
type ImageNative = ImgHTMLAttributes<HTMLImageElement>;
type Props = LazyImageProps & ImageNative;

export const LazyImg = ({
    src,
    onLazyLoad,
    ...imgProps
}: Props): JSX.Element => {
    const node = useRef<HTMLImageElement>(null);

    const [currentSrc, setCurrentSrc] = useState(
        "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIwIiBoZWlnaHQ9IjMyMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiLz4="
    );
    const [isLoaded, setIsLoaded] = useState(false);

    useEffect(() => {
        if (isLoaded) {
            return;
        }
        const observer = new IntersectionObserver((entries) => {
            entries.forEach((entry) => {
                if (entry.isIntersecting) {
                    setCurrentSrc(src);
                    setIsLoaded(true);

                    if (node.current) onLazyLoad(node.current);
                }
            });
        });

        if (node.current) observer.observe(node.current);

        return () => {
            observer.disconnect();
        };
    }, [src, onLazyLoad, isLoaded]);

    return <img ref={node} src={currentSrc} {...imgProps} />;
};

Siento que mi solución fue “inocente”, pero aquí va:

Paso 1:

Le añadí el evento onload al nodo DOM enviado por parámetro.
😁. Ese es el único paso.
El código está medio ilegible… pero si a alguien le interesa leer: