Search code examples
javascripthtmlcssfetch-api

How do I make data from a fetch display properly?


I'm trying to fetch data from three different endpoints which works, but when getting the name of the episode "undefined" is returned instead. I'm using the Rick and Morty API for this website. The link to the episodes endpoint https://rickandmortyapi.com/documentation/#episode-schema . Any help would be greatly appreciated.

function getCharacters(done) {
    fetch("https://rickandmortyapi.com/api/character")
        .then(response => response.json())
        .then(data => {
            done(data);
        })
        .catch(error => {
            console.error("Error fetching characters:", error);
        });
}

function getLastKnowLocation(done) {
    fetch("https://rickandmortyapi.com/api/location")
        .then(response => response.json())
        .then(data => {
            done(data);
        })
        .catch(error => {
            console.error("Error fetching last known locations:", error);
        });
}

function getEpisode(done) {
    fetch("https://rickandmortyapi.com/api/episode")
        .then(response => response.json())
        .then(data => {
            done(data);
        })
        .catch(error => {
            console.error("Error fetching episodes:", error);
        });
}


getCharacters(data => {
    data.results.forEach(item => {
        const article = document.createRange().createContextualFragment(/*html*/`
        <article class="character-card">
            <div class="image-container">
                <img src="${item.image}" alt="Character">
            </div>
            <div class="character-info">
                <div class="section">
                    <h2>${item.name}</h2>
                    <span class="status">${item.status} - ${item.species}</span>
                </div>
                <div class="section">
                    <span class="greytext">Last known location:</span>
                    <span>${item.location.name}</span>
                </div>
                <div class="section">
                    <span class="greytext">First seen in:</span>
                    <span>${item.episode.name}</span>
                </div>
            </div>
        </article>
        `);

        const main = document.querySelector("main");
        main.append(article);
    });
});
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

h1{
    text-align: center;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

main {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 18px;
}

.image-container{
    display: flex;
}

.image-container img{
    width: 229.2px;
    height: 220px;
}

article{
    padding: 10px;
    box-shadow: 0px 0px 1px #000;
}

.character-card{
    display: flex;
    margin: 13.5px;
}

.character-info{
    display: flex;
    flex-direction: column;
    padding: 13.5px;
    position: relative;
}

.section{
    display: flex;
    flex-direction: column;
    width: 343.8px;
    height: 64.33px;
}

.greytext{
    color: rgb(158,158,158);
}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Rick and Morty API</title>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <h1>Rick and Morty API</h1>
    <main></main>

    <script src="index.js"></script>
</body>

</html>


Solution

  • Each character has an array of episode URL strings. You can grab the first one and fetch the info for that episode in a nested callback.

    function getCharacters(done) {
      fetch("https://rickandmortyapi.com/api/character")
        .then(response => response.json())
        .then(data => {
          done(data);
        })
        .catch(error => {
          console.error("Error fetching characters:", error);
        });
    }
    
    function getLastKnowLocation(done) {
      fetch("https://rickandmortyapi.com/api/location")
        .then(response => response.json())
        .then(data => {
          done(data);
        })
        .catch(error => {
          console.error("Error fetching last known locations:", error);
        });
    }
    
    const main = document.querySelector("main");
    
    getCharacters(data => {
      data.results.forEach(character => {
        fetch(character.episode[0])
          .then(response => response.json())
          .then(episode => {
            main.insertAdjacentHTML('beforeend', `
              <article class="character-card">
                <div class="image-container">
                  <img src="${character.image}" alt="Character">
                </div>
                <div class="character-info">
                  <div class="section">
                    <h2>${character.name}</h2>
                    <span class="status">${character.status} - ${character.species}</span>
                  </div>
                  <div class="section">
                    <span class="greytext">Last known location:</span>
                    <span>${character.location.name}</span>
                  </div>
                  <div class="section">
                    <span class="greytext">First seen in:</span>
                    <span>${episode.name}</span>
                  </div>
                </div>
              </article>
            `);
          })
      });
    });
    *,h1{font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}.character-info,.image-container,.section{display:flex}*{margin:0;padding:0;box-sizing:border-box}h1{text-align:center}main{display:grid;grid-template-columns:1fr 1fr 1fr;gap:18px}.image-container img{width:229.2px;height:220px}article{padding:10px;box-shadow:0 0 1px #000}.character-card{display:flex;margin:13.5px}.character-info{flex-direction:column;padding:13.5px;position:relative}.section{flex-direction:column;width:343.8px;height:64.33px}.greytext{color:#9e9e9e}
    <title>Rick and Morty API</title>
    <h1>Rick and Morty API</h1>
    <main></main>

    Async version

    Here is an async/await version that is easier to read:

    const mainEl = document.querySelector("main");
    
    const main = async () => {
      const characters = await fetchAllCharacters();
      for (let character of characters) {
        const episode = await fetchJsonAsync(character.episode[0]);
        mainEl.insertAdjacentHTML('beforeend', renderCharacterHTML(character, episode));
      }
    };
    
    const renderCharacterHTML = (character, episode) => `
      <article class="character-card">
        <div class="image-container">
          <img src="${character.image}" alt="Character">
        </div>
        <div class="character-info">
          <div class="section">
            <h2>${character.name}</h2>
            <span class="status">${character.status} - ${character.species}</span>
          </div>
          <div class="section">
            <span class="greytext">Last known location:</span>
            <span>${character.location.name}</span>
          </div>
          <div class="section">
            <span class="greytext">First seen in:</span>
            <span>${episode.name}</span>
          </div>
        </div>
      </article>
    `;
    
    const fetchAllCharacters = async () => {
      const response = await fetchJsonAsync('https://rickandmortyapi.com/api/character');
      return response.results;
    };
    
    const fetchJsonAsync = async (url) => {
      try {
        let response = await fetch(url);
        if (!response.ok) throw new Error(response.statusText);
        return response.json();
      } catch (err) {
        console.error('Error fetching JSON:', err);
      }
    };
    
    main(); // Call main function
    *,h1{font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}.character-info,.image-container,.section{display:flex}*{margin:0;padding:0;box-sizing:border-box}h1{text-align:center}main{display:grid;grid-template-columns:1fr 1fr 1fr;gap:18px}.image-container img{width:229.2px;height:220px}article{padding:10px;box-shadow:0 0 1px #000}.character-card{display:flex;margin:13.5px}.character-info{flex-direction:column;padding:13.5px;position:relative}.section{flex-direction:column;width:343.8px;height:64.33px}.greytext{color:#9e9e9e}
    <title>Rick and Morty API</title>
    <h1>Rick and Morty API</h1>
    <main></main>