No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

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 鈥榬eaccione鈥 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 鈥渋nocente鈥, 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: