No tienes acceso a esta clase

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

Componentes que extienden elementos DOM

11/16
Recursos

Aportes 6

Preguntas 3

Ordenar por:

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

Yo utilice interface y herede de ImgHTMLAttributes<HTMLImageElement>

interface Props extends ImgHTMLAttributes<HTMLImageElement> {
}

con esto ya funcionaría pero tendriamos que validar que src tenga algun valor cada vez que queramos utilizarlo utlizarlo por que en la imagenes src: string | undefined, pero esto lo solucione definiendo src solamente como string

interface Props extends ImgHTMLAttributes<HTMLImageElement> {
    src: string
}

Componentes que extienden elementos DOM

Nuestros componentes evolucionan, por lo que es importante crear componentes escalables que puedan tener funcionalidades más complejas en el futuro… Vamos a ver como podemos hacer que nuestro componente que hemos trabajado sea un componente más global, escalable, y reutilizable.

De RandomFox.tsx a LazyImage.tsx

import { useRef, useEffect, useState } from "react"

type Props = { image: string }

export default function LazyImage( { image }: Props ):JSX.Element {  
  const node = useRef<HTMLImageElement> (null)
  const [src, setSrc] = useState<string>("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIwIiBoZWlnaHQ9IjMyMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiLz4=")

  useEffect(() => {
    const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          setSrc(image)
        }
      })
    })

    if (node.current) {
      observer.observe(node.current)
    }
    
    return () => {
      observer.disconnect()
    }

  },[image])

  return (
    <img ref={node} src={src} className="w-80 h-auto rounded-lg bg-gray-300" />
  )
}

Listo, ahora supongamos que una de nuestras compañeras desarrolladoras necesita añadir el evento onCLick={} en nuestro componente, veamos como hacer esto:
App.tsx

function App():JSX.Element {
  ...

  return (
    <main className="App">
      ...
      {
        foxes.map((fox, index) => (
          <div key={index} className="p-4">
						{/* Esto nos debería dar un error */}
            <LazyImage image={fox} **onClick={() => console.log('Hola')}** />
          </div>
        ))
      }

    </main>
  )
}

LazyImage.tsx

// actualizamos los tipos añadiendo el onClick
type Props = { image: string, onClick: () => void }

export default function LazyImage( { image, onClick }: Props ):JSX.Element {  
  ...

  return (
    <img ref={node} src={src} className="w-80 h-auto rounded-lg bg-gray-300" **onClick={onClick}** />
  )
}

Si probamos el componente, veremos que funciona, pero, ¿acaso esto escala? Recordemos que como desarrolladores tenemos que hacer componentes globales, escalables, y reutilizables, por ello, podemos cambiar esto para en caso de que alguna de nuestras compañeras quiera añadir funcionalidad extra, esta sea sencilla de implementar. Veamos como:

LazyImage.tsx

/* Importamos el tipo de dato que nos aparece cuando hacemos hover cualquiera
de los eventos de image (como aprendimos antes) */
import type { ImgHTMLAttributes } from "react"

// Creamos dos nuevos tipos de datos
type LazyImageProps = { image: string }
type ImageNativeTypes = ImgHTMLAttributes<HTMLImageElement>

// Los sumamos, haciendo que estos sean un solo tipo de dato
type Props = LazyImageProps & ImageNativeTypes

// recibimos todas las propiedades que soportamos de img con el spread operator
export default function LazyImage( { image, ...imgProps}: **Props** ):JSX.Element {  
  return (
		{/* todas las propiedades que recibe un img soportan automática e implícitamente */}
    <img ... {...imgProps} />
  )
}

Ya de esta forma estamos recibiendo todos los parámetros que puede recibir un <img /> en React (title, onClick, className, etc). Y como queremos que nuestro componente sea reutilizable, los estilos y personalización del componente deberían ir en el padre. Vamos a hacer más cambios importantes:

LazyImage.tsx

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

// actualizamos image para que ahora sea src para no causar confusión
**type LazyImageProps = { src: string }**
type ImageNativeTypes = ImgHTMLAttributes<HTMLImageElement>

type Props = LazyImageProps & ImageNativeTypes

export default function LazyImage( { **src**, ...imgProps }: Props ):JSX.Element {  
  const node = useRef<HTMLImageElement> (null)

	// a src lo cambiaremos a currentSrc paraevitar sobreescritura
  const **[currentSrc, setCurrentSrc]** = useState<string>("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIwIiBoZWlnaHQ9IjMyMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiLz4=")

	// corregimos los datos a recibir
  useEffect(() => {
    const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          **setCurrentSrc(src)**
        }
      })
    })
  
    if (node.current) {
      observer.observe(node.current)
    }
    
    return () => {
      observer.disconnect()
    }

  },[**src**])

  return (
    **<img ref={node} src={currentSrc} {...imgProps} />**
  )
}

App.tsx

import { useState } from "react"
import LazyImage from "./components/LazyImage"
import type { MouseEvent } from "react"

const random = (num: number):number => {
  return Math.floor(Math.random() * num) + 1
}

function App():JSX.Element {
  const [foxes, setFoxes] = useState<string[]> ([])

  const addNewFox = (event: MouseEvent<HTMLButtonElement>):void => {
    event.preventDefault()

    const newImage: string = `https://randomfox.ca/images/${random(123)}.jpg`
    setFoxes([
      ...foxes,
      newImage
    ])
  }

  return (
    <main className="App">
      <h1 className="text-3xl font-bold text-indigo-600 p-6">Random Fox</h1>
      <button onClick={addNewFox} className="">Añadir zorro</button>
      {
        foxes.map((fox, index) => (
          <div key={index} className="p-4">
						{/* Utilizamos el atributo src y añadimos la personalización en el componente padre */}
            <LazyImage **scr={fox}** className="w-80 h-auto rounded-lg bg-gray-300" onClick={() => console.log('Hola')} />
          </div>
        ))
      }

    </main>
  )
}

export default App

Hemos logrado crear un componente global, escalable, y reutilizable. El cual utiliza las convenciones más convenientes para que nuestro equipo de trabajo tenga una buena experiencia de desarrollo al momento de utilizar nuestro código, haciéndolo fácil de entender y fácil de mantener a lo largo del tiempo.

Excelente Explicacion…

Lo mejor de platzi !

Me parece más cómodo usar interfaces y extender los atributos del DOM. Pero si es una muy buena manera de entender las mejores prácticas cuando tenemos componentes que son extensiones de los nodos del DOM nativo. ```js interface LazyImageProps extends HTMLAttributes<HTMLImageElement> { src: string; } // Simplemente usamos parametros rest export const LazyImage = ({ src, ...rest }: LazyImageProps): JSX.Element => { // Código... return ; } ```interface LazyImageProps extends HTMLAttributes\<HTMLImageElement> {  src: string;}

Brutal