No tienes acceso a esta clase

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

Consultas a la API para escribir HTML dinámico

11/20
Recursos

Aportes 41

Preguntas 16

Ordenar por:

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

una cosita si uds al momento de darle clic a los botones de agregar a Favorito y no les agrega pa´nada, verifiquen que no tengan el End Poin de
limit=2, si no puede acumular una basta cantidad de Michis al momento que lo quiten 😄

Una forma optima de agregar elementos al DOM sin hacer una por cada elemento.

Después de mucho tiempo volví a retomar de nuevo la clase y ahora se me hace más fácil entender todo esto.
Dejo una captura de pantalla de mi proyecto y el repo. se aprecia mejor en el Repo la Imagen:
GitHub: https://github.com/SoterRamirez/DogStore

Para evitar hardcodear todos los botones, cree listas de los botones, guardo datos entre nodos para leerlos y asi tener funciones mas genericas.

sobre este ciclo coloco los datos sobre el elemento padre entre los atributos.

En esta imagen coloco un evento sobre todos los botones de los michis random, luego en el mismo ciclo determino cual fue el elemento del evento y obtengo de su padre el id del michi a guardar

Para entender mejor el problema del minuto 18:40:
Cuando le asignamos a

btn.onclick = mifuncion()

lo que hacemos es asignarle el resultado de nuestra función; básicamente le estamos diciendo “Te asignaré lo que resulte si ejecutamos mifuncion()”, por eso era que siempre se guardaban en favoritos. Cuando lo envolvemos en una función anónima (cuando haces () => {}, es anónima porque es realmente una función como esta: function mifuncion(){}, solo que sin un nombre) lo que hacemos es que el propio onclick sea una función (que solo se ejecutará cuando sea llamada). De hecho, si luego de hacer lo que JuanDa hizo:

btn.onclick = () => funcion()

hacemos

btn.onclick()

La función se va a ejecutar de igual manera, como si hicieras

btn.onclick =  funcion()

Hola, ahí va mi progreso. Lo que hago es agregar caninos dinamicamente y el corazon es para agregarlos a la lista de favoritos

Les comparto otra manera de añadir los michis al DOM!

const API_URL_FAVOTITES = ‘https://api.thecatapi.com/v1/favourites?api_key=c08d415f-dea7-4a38-bb28-7b2188202e46’; para favoritos sin (limit=2) OJO

Amee las reacciones que tiene Juan DC cuando miraba un gatito Jajajaj

Hola, quizás estarás pensando que es un poco tedioso tener que declarar cada imagen con id e igual que los botones si por ejemplo, queremos tener 10 imágenes… Para eso podemos realizar un código parecido al cargar la sección de favorito, con la diferencia que al button le vamos agregar un addEventListener de ‘click’ y hacemos el llamado a la función de salvar en favoritos, para poder pasar el ID de la imagen utilizamos la el método bind

Dejo la imagen de ejemplo, en mi caso estoy con la API de perritos

async function loadRandomDog() {
    const response = await fetch(URL_RANDOM)
    const data = await response.json();
    const randomSection = document.querySelector('#randomDogs');

    if(response.status !== 200) {
        spanErro.innerHTML = "Hubo un error: " + response.status;
    } else {

        data.forEach(dog => {
            const article = document.createElement('article');
            const img = document.createElement('img')
            const button = document.createElement('button')
            const btnText = document.createTextNode('Guardar en Favoritos')
            img.src = dog.url;

            button.addEventListener('click', saveFavoritesDog.bind('idDog',dog.id))
            button.appendChild(btnText);
            article.appendChild(img);
            article.appendChild(button);
            
            randomSection.appendChild(article);
        });
    }
}

Espero que les funcione e igual recibir mejoras para este código

Mi avance…demoré un poco pero me sorprende lo mágico que se puede hacer con solo JavaScript puro…ya quisero aprender REACT ❤

Imposible sacarlo de favoritos a ese Michi 😄

Aclarando las tres formas de insertar texto en un nodo en el DOM. Existen 4 formas de agregar texto:

createTextNode
innerText
innerHTML
textContent

createTextNode escapará de cualquier cadena y las mostrará tal como son, mientras que innerHTML podría convertir cadenas similares a html en un DOM. Si no desea eso puede usar textContent o innerText que es soportado en IE. Tomar en cuenta también que innerText es consciente de la apariencia renderizada del texto, mientras que textContent no lo es.

https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText

También pueden usar el Try / Catch y con map crear un cliclo, ejemplo-

 try{
        const michi = await saveMichi();
        let view = `
            ${data.map(michiup => `
            <article id="article">
            <div>
                <img src='${michiup.image.url}'">
            </div>
            <button class="favorito like"><img src="./dislike-icon.png"></div></button>
        </article>
            `)}
        `;
        favoriteMichi.innerHTML = view
    } catch(error){
        console.log(error)
    }

Duré horas tratando de debuggear un error en mi código al agregar nuevas mascotas al favorito, despeje un rato la mente y luego pude solucionarlo 🚀

comparto documentación sobre el parsing
https://developer.mozilla.org/en-US/docs/Glossary/Parse

Les comparto mi progreso

Muy guapa la 2da foto

👉☀️🐈

Esta clase es excelente por todas las posibilidades que te otorga a la hora de jugar con descubrir que puede hacer uno al crear html dinamico. Yo probe una alternativa previo a ver esta clase en la cual en vez de seguir usando el onClick pase a asignarle un eventListener a cada uno de los buttons y quedo de esta forma:

//Realiza un fetch y provee una imagen aleatoria de un gatito, una vez obtenida la asigna al elemento image por medio de su src, ademas usa randomCatName para asignarle un nombre
async function loadRandomMichis() {
        const res = await fetch(`${randomAPI}${queryLimit}&${key}${keyAPI}`);
        const data = await res.json();

        console.log({name: "random",datos: data})
        if (res.status !== 200) {
            spanError.innerHTML = `Hubo un error ${res.status}`
        } else {
        // Traemos el container con el que estamos trabajando
        const container = document.getElementById('random-cats');

        //Generamos nombres aleatorios provenientes de un array para el numero de imagenes con el que estemos trabajando
        const title = document.getElementById('cat-title');
        let titleContent = "";
        for (let i = 1; i <= data.length; i++) {
            let catName = randomCatName(catNames);
            while (titleContent.includes(catName)) {
                catName = randomCatName(catNames);
            }
            if (i > 1) {
                titleContent = titleContent + ", " + catName;
            } else {
                titleContent = catName;
            }
        }
        // Generamos un array con los nombres asignados a cada imagen
        const titleArray = titleContent.split(', ');
        //Limpiamos el container entre llamada y llamada
        container.innerHTML = '';

        // Generamos un article que consta de una imagen y un boton para agregar a favs por cada uno de los michis que importamos
        // Asignamos un id dinamico a cada img y cada button por medio de un incrementor en caso que lo necesitemos mas adelante (Css por ej)
        let incrementor = 1;
        for (element of data) {
            const article = document.createElement('article')
            const fav_button = document.createElement('button')
            fav_button.type = 'button'
            fav_button.id = `fav_button-img${incrementor}`
            fav_button.className = 'choice-button'
            fav_button.innerHTML = 'Save in favs!'
            const img = document.createElement('img');
            img.id = `img${incrementor}`
            img.src = element.url;
            img.dataset.id = element.id;
        //Basandonos en el nombre que se le asigno en el titulo se lo alojamos en un dataset para enviarlo mas adelante
            img.dataset.catName = titleArray[incrementor - 1];
            img.width = 300;
            img.height = 300;
            article.appendChild(img)
            article.appendChild(fav_button)
            container.appendChild(article)
        // Los guardamos en favs enviando tanto su id como el nombre aleatorio que se le brindo en la funcion randomCatName
            fav_button.addEventListener('click', () => {
                saveFavouriteMichis(img.dataset.id, img.dataset.catName);
            });

            incrementor++
        }
        title.innerHTML = titleContent;
    }
};

Yo lo hice de esta manera

async function dogsFovurites (subId) {
        let favDogs = d.querySelector('#favDogs');
        const res = await fetch(`${endpoint.URL_API_FAVOURITES}?sub_id=`+subId)
        console.log(res);
        const data = await res.json();
        try {
            let data_nuevo = data.slice(data.length-4);
            let datos_fav = `
            ${data_nuevo.map (dog_fav =>
                ` 
                <div class="col mb-3 me-2 ms-3" style="float: left;">
                    <div class="card " style="width: 18rem;" >
                        <img src="${dog_fav.image.url}" class="card-img-top img_cambia" alt="..." width="300" height="200">
                        <div class="card-body">
                           <button type="button" id="" class="btn btn-primary">${dog_fav.id}</button>
                        </div>
                    </div>
                </div>
                `)}`;
                favDogs.innerHTML = datos_fav.replace(/,/g,"") 
        } catch (error) {
            const errorNode = document.querySelector('#error');
            errorNode.innerText = `Error: ${error.message}`;
        }
    }

Yo buscando que esta mal en mi código porque no se agregan los gatitos a favoritos y tenia un end poin limit=4, ahora tengo como 60 favoritos 😮

Una forma que considero mas fácil de insertar código en el html es mediante la función insertAdjacentHTML funciona prácticamente igual que el innerHTML pero sin el eliminar el html que contiene el contenedor.

async function loadRandomMichis() {
    const reponse = await fetch(API_URL_RANDOM);
    const data = await reponse.json();
    const randomMichis_container = document.getElementById("randomMichis_container");
    if (reponse.status !== 200) {
        console.log("error");
        randomMichis_container.innerHTML = `<article>Error ${reponse.status}: ${data.message}</article>`
    } else {
        randomMichis_container.innerHTML = ""
        data.map((cat) => {
            randomMichis_container.insertAdjacentHTML("beforeend", `
                <article>
                    <div class="randomMichis_img">
                        <img src="${cat.url}" class="randomMichis_img" id="img" alt="imagen de los gatos">
                    </div>
                    <button id="${cat.id}" type="button"> Guardar en favoritos</button>
                </article>`)
            const button = document.getElementById(cat.id)
            button.addEventListener("click", () => saveFavorite(cat.id))
        })
    }
}
async function loadFavoritesMichis() {
    const response = await fetch(API_URL_FAVORITES);
    const data = await response.json();
    const favorites = document.getElementById("favorites");
    favorites.innerHTML = "";
    data.map(favorite => {
        favorites.insertAdjacentHTML("beforeend", `
        <article class="favorites_container">
        <div class="randomMichis_img">
        <img src="${favorite.image.url}" class="randomMichis_img" id="img" alt="imagen de los gatos">
        </div>
        <button type="button">Eliminar de favoritos</button>
        </article>
        `)
    })
}
const fetchData = async (urlApi) => {
  const res = await fetch(urlApi);
  const data = await res.json();
  return {res, data};
};

async function loadRandomDogs(){
  
  const {res, data} = await fetchData(URL_API_RANDOM);
  console.log('loadRandomDogs');
  console.log (data);

  if(res.status !== 200){
    const spanError = document.getElementById('error')
    spanError.innerHTML = 'Hubo un error ' + res.status;
  }else{
    const img1 = document.getElementById('img1');
    const img2 = document.getElementById('img2');
    const img3 = document.getElementById('img3');
    const btn1 = document.getElementById('btn1');
    const btn2 = document.getElementById('btn2');
    const btn3 = document.getElementById('btn3');

    
    img1.src = data[0].url;
    img2.src = data[1].url;
    img3.src = data[2].url;

    btn1.onclick = () => saveFavouriteDog(data[0].id);
    btn2.onclick = () => saveFavouriteDog(data[1].id);
    btn3.onclick = () => saveFavouriteDog(data[2].id);
  }
};

async function loadFavouritesDogs(){
  
  const {res, data} = await fetchData(URL_API_FAVOURITE);
  console.log('loadFavouritesDogs')

  console.log (data);
  if(res.status !== 200){
    const spanError = document.getElementById('error')
    spanError.innerHTML = 'Hubo un error ' + res.status + res.message;
  }else{
    data.forEach(dog =>{
      const section = document.getElementById('favoriteDogs');
      const article = document.createElement('article');
      const img = document.createElement('img');
      const btn = document.createElement('button');
      const btnText = document.createTextNode('Eliminar de favoritos');
      
      img.src = dog.image.url;
      btn.appendChild(btnText);
      article.appendChild(img);
      article.appendChild(btn);
      section.appendChild(article);
    });

    
  }
};

async function saveFavouriteDog(id){
  const { res, data } = await fetch(URL_API_FAVOURITE,{
    method: "POST",
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      image_id:id
    })
  });
  console.log('guardar favoritos')
  console.log(res);
}

loadRandomDogs();
loadFavouritesDogs();

Lo mejor de esta clase, Juan dándole a favoritos a todos los michis jajajajaja!

usando eventos del body:

main.js

let status;
fetch('https://api.thecatapi.com/v1/images/search?limit=5&api_key=c08d415f-dea7-4a38-bb28-7b2188202e46')
    .then(res => {
        let miRes = res.json()
        status = res.status
        return miRes
    })
    .then(data => {
        const IMG = data.map(o => {
            console.log(o.id)
        const template = document.createElement('template');
        template.innerHTML =`
        <figure data-url='${o.id}'>
            <address>STATUS: ${status}</address>
            <img src='${o.url}'/>
            <button>guardar</button>
        </figure>
        `;
        return template.content.cloneNode(true)
        });
        document.querySelector('main').append(...IMG)
    })

document.body.addEventListener('click',(e)=> {
    if(e.target.textContent == 'guardar'){
        let url = e.target.parentNode.dataset.url;
        fetch('https://api.thecatapi.com/v1/favourites?api_key=c08d415f-dea7-4a38-bb28-7b2188202e46',{
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                image_id: url
            })
        })
    }

})

fetch('https://api.thecatapi.com/v1/favourites?limit=100&api_key=c08d415f-dea7-4a38-bb28-7b2188202e46')
    .then(res => res.json())
    .then(data => {
        const IMG = data.map(o => {
        const template = document.createElement('template');
        template.innerHTML =`
        <figure>
            <img src='${o.image.url}'/>
        </figure>
        `;
        return template.content.cloneNode(true)
        });

        document.querySelector('aside').append(...IMG)
    });

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>API REST</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
<style>
figure {
    display:grid;
    grid-template-rows:max-content 1fr 40px;
}
button{
    cursor:pointer
}
button:hover {
    background:#111;
    opacity:.7;
    color:#fff;
}
address {
    color:#fff;
}
body{
    background:#222;
}
h2{ color: #fff; font-size: 2rem; text-align: center}
img{ width: 100%;height: 200px}
main { border-bottom: 2px solid #fff}
main, aside{
    display:grid;
    gap: 10px;
    grid-template-columns:repeat(auto-fill,minmax(200px,1fr))
}
</style>
    <script src="./main.js" defer></script>
</head>
<body>
    <h2>cats random</h2>
    <main></main>
    <h2>cats favorites</h2>
    <aside></aside>
</body>
</html>

Esto es lo que he hecho en este curso
https://emazou.github.io/the-cat-api/


asi me senti porque aun no se manipular el DOM

Trabaje con data-attributes en el html para poder tomar los ID
.
html
.
.
Js.
.

<!-- ... -->
  <body>
    <h1>Cats API App</h1>

    <section id="randomCats">
      <h2>Random Cats</h2>
      <span id="error"></span>
      <article>
        <img class="cat-image" alt="Foto de gatito 1" style="width:16rem">
        <button class="save-action">Save to favorites</button>
      </article>
      <article>
        <img class="cat-image" alt="Foto de gatito 2" style="width:16rem">
        <button class="save-action">Save to favorites</button>
      </article>
      <article>
        <img class="cat-image" alt="Foto de gatito 3" style="width:16rem">
        <button class="save-action">Save to favorites</button>
      </article>
      <button onclick="pickACat()">Pick another CAThino</button>
    </section>
    
    <section id="favoriteCats">
      <h2>favorites Cats</h2>
    </section>

    <script src="main.js"></script>
  </body>
<!-- ... -->

.
JS

const API_URL = "https://api.thecatapi.com/v1";
const API_KEY = "your-api-key";
const endpoints = {
  API_URL_LIMIT_SEARCH: (limit = 3) =>
    `${API_URL}/images/search?limit=${limit}&api_key=${API_KEY}`,
  API_URL_FAVOURITES: `${API_URL}/favourites?api_key=${API_KEY}`,
};

const spanError = document.getElementById("error");

const pickACat = async () => {
  const response = await fetch(endpoints.API_URL_LIMIT_SEARCH(3));

  try {
    const data = await response.json();
    const imgTags = document.querySelectorAll(".cat-image");
    const btnTags = document.querySelectorAll(".save-action");
    imgTags.forEach((element, key) => (element.src = data[key].url));
    btnTags.forEach(
      (btn, key) => (btn.onclick = () => saveAFavouriteCat(data[key].id))
    );
  } catch (error) {
    if (response.status !== 200)
      return (spanError.innerText = "An error has occurred," + data.message);
  }
};

const loadFavouriteCats = async () => {
  const response = await fetch(endpoints.API_URL_FAVOURITES);
  const data = await response.json();

  console.log(
    "response pick favourites >>>",
    response,
    data ? data : "no data"
  );

  data.forEach((cat) => {
    const section = document.getElementById("favoriteCats");
    const article = document.createElement("article");
    const image = document.createElement("img");
    const button = document.createElement("button");
    const btnText = document.createTextNode("Remove from favourites");

    image.src = cat.image.url;
    image.style = "width:16rem";
    button.appendChild(btnText);
    article.appendChild(image);
    article.appendChild(button);
    section.appendChild(article);
  });
};

const saveAFavouriteCat = async (id) => {
  const response = await fetch(endpoints.API_URL_FAVOURITES, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      image_id: id,
    }),
  });
  const data = await response.json();

  console.log("save", response);

  if (response.status !== 200)
    return (spanError.innerText = "An error has occurred," + data.message);
};

pickACat();
loadFavouriteCats();

me estoy haciendo el de perriotos 😉

console.log("estamos listos");

const key = '93281cee-4d7f-4c6d-abc9-62cc90323eb1';
const API_URL_RANDOM = "https://api.thedogapi.com/v1/images/search?limit=6&api_key="+key;
const API_URL_FAVORITES = "https://api.thedogapi.com/v1/favourites";

const getRandomImage = async() => {
    const res = await fetch(API_URL_RANDOM);
    const data = await res.json();
        console.log("get Image")
        console.log(data);
    if(res.status !== 200){
        console.log("Error: "+ res.status);
    }else {
        const img1= document.getElementById('img1');
        const img2= document.getElementById('img2');
        const img3= document.getElementById('img3');
        const img4= document.getElementById('img4');
        const img5= document.getElementById('img5');
        const img6= document.getElementById('img6');
        img1.src = data[0].url
        img2.src = data[1].url
        img3.src = data[2].url
        img4.src = data[3].url
        img5.src = data[4].url
        img6.src = data[5].url
    
    };
};

const getImagesFavorites = async() => {
    const res = await fetch(API_URL_FAVORITES, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
            'x-api-key': '93281cee-4d7f-4c6d-abc9-62cc90323eb1'
        },
    });
    const data = await res.json();

        console.log("get Favorites")
        console.log(data);

    if(res.status !== 200){
        console.log("Error: "+ res.status +" "+ data.message);
    }
};

const saveImageFavorites = async (value) => {
    const res = await fetch(API_URL_FAVORITES, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'x-api-key': '93281cee-4d7f-4c6d-abc9-62cc90323eb1',
        },
        body: JSON.stringify({
            "image_id": "UyN27ZccR",
        }),
    });
    const data = await res.json();
    console.log('save');
    console.log(res);
    console.log("cambiando el color"+value);

    const element = document.getElementById('start'+value);
    element.classList.toggle('start-active');

    if(res.status !== 200){
        console.log("Error: "+ res.status +" "+ data.message)
    }
};

const another =() => {
    getImage();
};

getRandomImage();
getImagesFavorites();
    

Quiero aportar el código de la clase, pero usando POO y escribiendolo en TypeScript 😄. Posdata: La función loadRandomMichis del profe la separé en dos funciones una que se dedique al DOM de favoriteImages y la otra se encarga de hacer el fetch y obtener la data. Lo hice con el objetivo de seguir el concepto de one job only.

import { Cat } from "./../models/cat.model";
import { RequestFailed } from "./../models/request.failed.model";
import { FavoriteCat } from "./../models/favorite.cat.model";

export class LoadCatImage {
  constructor (
    private readonly apiUrlRandom: string,
    private readonly apiUrlFavorites: string,
    private readonly elementImages: HTMLCollectionOf<Element>,
    private readonly spanError: HTMLElement | null,
    private readonly saveFavorite1: HTMLElement | null,
    private readonly saveFavorite2: HTMLElement | null,
  ) {
    this.randomImages = this.randomImages.bind(this);
    this.saveOnFavorites = this.saveOnFavorites.bind(this);
    this.favoriteImages = this.favoriteImages.bind(this);
  }

  private favoriteImagesDom (data: FavoriteCat[]): void {
    const section: HTMLElement | null = document.getElementById("favoriteCats");

    data.forEach(cat => {
      const article: HTMLElement = document.createElement("article");
      const imageElement: HTMLElement = document.createElement("img");
      const button: HTMLElement = document.createElement("button");
      const image: HTMLImageElement = imageElement as HTMLImageElement;
      const buttonText: Text = document.createTextNode("Get out cat picture from favorites");
      button.appendChild(buttonText); //Se le agrega un texto al botón


      article.appendChild(image);
      article.appendChild(button);

      if (section !== null) {
        section.appendChild(article);
      }

      image.src = cat.image.url;
      image.width = 150;
    });
  }

  async favoriteImages (): Promise<void> {
    const res: Response | null = await fetch(this.apiUrlFavorites);

    if (res.status === 200) {
      const data: FavoriteCat[]= await res.json();
      this.favoriteImagesDom(data);

    } else {
      this.catchError(res);
    }
  }

  async randomImages ():Promise<void> {
    const res: Response | null = await fetch(this.apiUrlRandom);

    if (res.status === 200) {
      const data: Cat[]= await res.json();
      this.handleEventFavoriteButton(data);
      // this.images = [];
      for (let index = 0; index < this.elementImages.length; index++) {
        const image = this.elementImages[index] as HTMLImageElement;

        image.src = data[index].url;
      }

    } else {
      this.catchError(res);
    }
  }

  private async catchError(res: Response | null): Promise<void> {
    if (this.spanError !== null && res !== null) {
      const dataError: RequestFailed = await res.json();
      this.spanError.innerText = `Something went wrong. Error ${dataError.status}. ${dataError.message}`;
    }
  }

  private handleEventFavoriteButton (favorites: Cat[]): void {
    if (this.saveFavorite1 !== null && this.saveFavorite2 !== null) {
      this.saveFavorite1.onclick = () => this.saveOnFavorites(favorites[0]);
      this.saveFavorite2.onclick = () => this.saveOnFavorites(favorites[1]);
    }
  }

  async saveOnFavorites (favorite: Cat): Promise<void> {
    let idImage: string = favorite.id;

    const res: Response | null = await fetch(this.apiUrlFavorites, { //Cuando se quiere hacer una peticion
        //con fetch que no sea get se debe colocar las indicaciones de la peticion.
      method: "POST",
      headers: {
        "Content-Type": "application/json" //Lo indica la api.
      },
      body: JSON.stringify({
        image_id: idImage
      })
    });

    if (res.status === 200) {
      console.log(res);
    } else {
      this.catchError(res);
    }
  }
}

Este es mi aporte jaja 😄

<code> 
const d = document,
$fragment = d.createDocumentFragment();

const api_url_random = 'https://api.thecatapi.com/v1/images/search?limit=10&',
api_url_favorites = 'https://api.thecatapi.com/v1/favourites',
api_key = "e06a0153-5e69-4361-a4da-37f0698786d4";

const createMichi = (data, contentBtn) => {
    const $container = d.createElement('article'),
    $image = d.createElement('img'),
    $btnAdd = d.createElement('button');
    $container.classList.add('michi-random');
    $image.src = data.url || data.image.url; 
    $image.alt = "Michi random";
    $image.id = data.id;
    if(contentBtn === "Eliminar michi de favoritos"){
        $btnAdd.classList.add("btn-delete");
    }else{
        $btnAdd.classList.add("btn-add");
    }
    $btnAdd.dataset.id = data.id;
    $btnAdd.textContent = contentBtn;
    $container.append($image, $btnAdd);

    return $container;
};

const getData = async (url) =>{
    try{
        let res = await fetch(url),
        data = await res.json();

        data.forEach(el => {
            const $michi = createMichi(el, "Agregar michi a favoritos");
            $fragment.append($michi);
        });
        d.querySelector('#randomMichis').innerHTML = '';
        d.querySelector('#randomMichis').append($fragment);
        
    }catch(err){

        console.error(err);
    }
}

const getFavorites = async (url) => {
    try{
        let res = await fetch(url),
        data = await res.json();
        
        if(!data[0]){
            d.querySelector('#favoritesMichis').innerHTML = `
                <h3 style="color: red; font-size: 2.2rem; text-align: center;">No hay michis D:</h3>
            `;
        }else{
            console.log(data);
            data.forEach(el => {
                const $michi = createMichi(el, "Eliminar michi de favoritos");
                console.log(el)
                $fragment.append($michi);
            });
            
            console.log($fragment);
            d.querySelector('#favoritesMichis').innerHTML = '';
            d.querySelector('#favoritesMichis').append($fragment);
        }   
    }catch(err){

        console.error(err);
    }
}

const saveFavoriteMichi = async(url, id) => {
    try{
        let res = await fetch(url,{
            method: 'POST',
            headers: {
                "Content-type": "application/json",
            },
            body: JSON.stringify({
                image_id: String(id),
            }) 
        })
        getFavorites(`${api_url_favorites}?api_key=${api_key}`);
        console.log(res);

    }catch(err){

    }
}

const deleteMichi = async (url) =>{
    try{
        let res = await fetch(url,{
            headers: { "x-api-key": api_key },
            method: "DELETE",
    })
    }catch(err){

    }
}

d.addEventListener('DOMContentLoaded', e => {
    getData(`${api_url_random}?api_key=${api_key}`);
    getFavorites(`${api_url_favorites}?api_key=${api_key}`);
});

d.addEventListener('click', e => {

    if(e.target.matches('.btn-reload')){
        getData(`${api_url_random}?api_key=${api_key}`);
    }

    if(e.target.matches('.btn-add')){
        let id = e.target.dataset.id;
        saveFavoriteMichi(`${api_url_favorites}?api_key=${api_key}`, id);
    }

    if(e.target.matches('.btn-delete')){
        let id = e.target.dataset.id,
        item = e.target;
        deleteMichi(`${api_url_favorites}/${id}`);
        item.parentNode.remove()
    }
})
![](https://static.platzi.com/media/user_upload/catsui-381ac210-b371-49d9-9198-e63b944e0c29.jpg)Mi solución
Hola cuando intento guardar un favorito, no obtengo error pero no lo guarda. Esta les la rta al POST Response {type: 'cors', url: 'https://api.thecatapi.com/v1/favourites?api\_key=li…yS3uvwx73SqHbVRJFgWvY9oaK2wNAjeeun4f0QNWltIZlOwmA', redirected: false, status: 200, ok: true, …}body: (...)bodyUsed: trueheaders: Headers {}ok: trueredirected: falsestatus: 200statusText: ""type: "cors"url: "https://api.thecatapi.com/v1/favourites?api\_key=live\_TidABUnvzog2nU8yS3uvwx73SqHbVRJFgWvY9oaK2wNAjeeun4f0QNWltIZlOwmA"\[\[Prototype]]: Response

Yo lo hice de esta forma

data.forEach((itemCat) => {
                console.log(itemCat);
                containerFavourites.insertAdjacentHTML(
                    'beforeend',
                    `
                    <article>
                        <img id=${itemCat.id} class="img-cat_favorite" width="300" height="300" alt="Foto gato aleatorio" src=${itemCat.image.url} />

                        <button class='button_delete-favourite' >Eliminar foto de favoritos</button>
                    </article>
`
                );
            });
        }

Intenté hacerlo con templates, pero solo mostraba a un solo elemento del array de favoritos y al último:

JAJAJAJ
ME dio mucha risa en el minuto 19:15 donde sale un señor arrastrando un gato
y el profesor dice:

un gatito que no quiere caminar

CRC === SRC

Hola! Yo hice el append de esta manera:

 console.log(API_URL)
    const res = await fetch(API_URL)
    const data = await res.json()
    const section = document.getElementById("section")

    //pruba de limitacion de el array.
    let ari = data.splice(0,3)

    const htmlElement = ari.forEach( item => {

        const article = document.createElement("article")

        const img = document.createElement("img")
        img.src = item.url
        img.id = item.id

        const btn = document.createElement("button")
        btn.onclick = () => saveFavoriteCat(item.id)
        const btnText = document.createTextNode("Agregar a favoritos")
        btn.appendChild(btnText)

        article.appendChild(img)
        article.appendChild(btn)
        section.appendChild(article)

    })

Así va mi código por ahora, estoy usando try y catch, para las funciones, así como manipulación del DOM desde JS.
Este es el HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="src/styles.css">
    <title>Gatitos Aleatorios</title>
</head>
<body>
    <h1>Michis App</h1>

    <button id="refresh-button">Actualizar</button>
    <h2>Gatitos Aleatorios</h2>
    <section id="randomCats">
        
    </section>
    <h2>Gatitos Favoritos</h2>
    <section id="favoritesMichis">

    </section>

        

    <script src="src/reto1.js"></script>
</body>
</html>

Y EL JS

const refreshButton = document.querySelector('#refresh-button')
const API_URL = 'https://api.thecatapi.com/v1/'
const API_KEY= 'api_key=live_Bb5fxkkALOgv5l6bnPVukl9NVfwQjy1v1FkbLITMDHRMzZRUT3BlTbY7Lbh1LaAl'
const spanError = document.querySelector('#error')
const buttons = document.querySelectorAll('button')
let HTTPStatus=[
{
        number: 200,
        link: 'https://t4.ftcdn.net/jpg/05/34/44/37/240_F_534443710_dLVbUS1MtquWRhdWQ0a1iN3M5aZpyLOq.jpg',
},
{
    number: 201, 
    link: 'https://t4.ftcdn.net/jpg/01/01/50/43/240_F_101504362_Y9uq3U32d3JdmyeWGFzU5aNCwUiLzkoO.jpg',
},
{
    number: 203, 
    link: 'https://www.onlinesolutionsgroup.de/wp-content/uploads/203-Non-Authoritative-Information-1000x600.jpg',
},
{
    number: 300, 
    link: 'https://www.onlinesolutionsgroup.de/wp-content/uploads/statuscode-300-multiple-choices-1.jpg',
},
{
    number: 307, 
    link: 'https://t4.ftcdn.net/jpg/02/38/85/91/240_F_238859191_dXYTC8lTaJ3nBpEYYI6UNaRjd3vTDNUh.jpg',
},

{
    number: 400, 
    link: 'https://as1.ftcdn.net/v2/jpg/01/35/88/24/1000_F_135882447_SeFbWwnJ7Ig5ZQ9mpG22NHBhs6L2fBVy.jpg',
},

{
    number: 401, 
    link: 'https://as1.ftcdn.net/v2/jpg/01/35/88/24/1000_F_135882440_5epLvLQRBM42f90SD8yyKQIPno03j5Ef.jpg',
},
 
{
    number: 403, 
    link: 'https://www.seokratie.de/wp-content/uploads/2022/06/AdobeStock_135882455-751x501.jpeg',
},
 
{
    number: 404, 
    link: 'https://www.onlinemarketingmonkey.be/wp-content/uploads/2022/05/Uitgelicht-404.jpg',
},
 
{
    number: 500,
    link: 'https://as1.ftcdn.net/v2/jpg/01/35/88/24/1000_F_135882460_jMCfN05mhPOm2C8MvqvYym0e4qj5cKAC.jpg',
},
 
{
    number: 503,
    link: 'https://as2.ftcdn.net/v2/jpg/03/61/45/09/1000_F_361450985_YulVwRLEJmn7S5cOVITufWX1czh9lGKA.jpg',
},

{
    number: 504,
    link: 'https://t3.ftcdn.net/jpg/00/91/06/04/240_F_91060405_sqGYr6VG208sMURrSdSlPcay3ds1mlyV.jpg',
},
]

refreshButton.addEventListener('click', () => {
    location.reload();
})

async function fetchData(urlApi) {
    const response = await fetch(urlApi);
    const status = response.status

    
    if(status !== 200){
        const resp = HTTPStatus.find(item => item.number === status)
        const imgStatus = document.createElement('img')
        imgStatus.className = 'imgStatus'
        imgStatus.src = resp.link

        spanError.appendChild(imgStatus);
        console.log('error')
    } else{
        
        const data = await response.json();
        return data
    }
}

const loadImg = async (urlApi) => {
    try {
        const data = await fetchData(`${urlApi}images/search?limit=3&${API_KEY}`);
        console.log(data, 'data')
        const allImg = [];
        data.forEach(item => {
            const image =document.createElement('img');
            image.src = item.url
            id = item.id
            
            const iconFavorite = document.createElement('img')
            iconFavorite.src = 'https://img.icons8.com/external-those-icons-lineal-color-those-icons/512/external-favorite-bookmarks-tags-those-icons-lineal-color-those-icons.png'
            iconFavorite.className = 'icon'
            
            const buttonFavorites = document.createElement('button')
            buttonFavorites.className = 'buttonFavorites'
            buttonFavorites.appendChild(iconFavorite)

            const card = document.createElement('article')
            card.append(image, buttonFavorites)
            
            allImg.push(card)
            
            buttonFavorites.addEventListener('click', () => saveFavouriteCats(API_URL, id))
            
    });
    const sectionRandomCats = document.querySelector('#randomCats')
    sectionRandomCats.append(...allImg)
    }
    catch(error){
    console.error(error)
    }
};

const loadFavouritesImg = async (urlApi) => {

    try {
        const data = await fetchData(`${urlApi}favourites?${API_KEY}`,);
        console.log(data, 'favorites data')
        const allimg = [];
        data.forEach(item => {
        const image =document.createElement('img');
        image.src = item.image.url
        
        const iconDeleteFavorite = document.createElement('img')
        iconDeleteFavorite.src = 'https://img.icons8.com/external-those-icons-lineal-color-those-icons/512/external-favorite-bookmarks-tags-those-icons-lineal-color-those-icons-2.png'
        iconDeleteFavorite.className = 'icon'
        
        const buttonDeleteFavorite = document.createElement('button')
        buttonDeleteFavorite.className = 'buttonFavorites'
        buttonDeleteFavorite.appendChild(iconDeleteFavorite)

        const card = document.createElement('article')
        card.append(image, buttonDeleteFavorite)
        
        allimg.push(card)
        console.log(allimg)
    const sectionRandomCats = document.querySelector('#favoritesMichis')
    sectionRandomCats.append(...allimg)
        
        // buttonDeleteFavorite.addEventListener('click', () => {
        // // deleteFavouriteCat(API_URL);
        // })
    });

    }
    catch(error){
    console.error(error)
    }
};

const saveFavouriteCats = async (urlApi, idImg) => {
    console.log(idImg, 'ids antes del try en save')
    try {
            const res = await fetch(`${urlApi}favourites?${API_KEY}`,{
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    image_id: idImg
                }), 
            });

            console.log('SaveCats', res)
        }
        catch(error){
        console.error(error)
        }
}

loadImg(API_URL);
loadFavouritesImg(API_URL);

Asi va mi página 😄: