Search code examples
javascripthtmldomcreateelement

how to prevent .createElement view duplication when array doesn't have duplication


let recipes = [];
let favouriteRecipes;
let ul = document.querySelector(".list-group");


const fetchRecipes = async function() {
  try {
    const res = await axios.get('https://api.spoonacular.com/recipes/complexSearch?apiKey=e7221d9e49e04040afd50e7b626e2f88&number=2');
    recipes.push(...res.data.results);
    createNewRecipe(recipes);
  } catch (error) {
    console.log(error);
  }
}

const createNewRecipe = function(recipes) {
  for (let recipe of recipes) {
    let li = document.createElement('li');
    li.textContent = recipe.title;
    li.id = recipe.id;
    li.className = "list-group-item";

    let img = document.createElement('img');
    img.src = recipe.image;
    img.className = "card-img-top";

    li.append(img);
    ul.append(li);

    let recipeId = document.getElementById(recipe.id).id;
    fetchIndividualRecipe(recipeId, li);
    addToFavourites(recipeId);
  }
}

const fetchIndividualRecipe = async function(recipeId, li) {
  try {
    let id = recipeId;
    const res = await axios.get(`https://api.spoonacular.com/recipes/${id}/information?apiKey=e7221d9e49e04040afd50e7b626e2f88&includeNutrition=true`)
    createRecipeDetails(res, li);
  } catch (error) {
    console.log(error);
  }
}

const createRecipeDetails = function(individualRecipeData, li) {
  let cookingTime = individualRecipeData.data.readyInMinutes;
  let calories;
  for (let nutrient of individualRecipeData.data.nutrition.nutrients) {
    if (nutrient.name === "Calories") {
      calories = nutrient.amount;
    }
  }
  let spanMinutes = document.createElement("span");
  let spanCalories = document.createElement("span");
  spanMinutes.className = "badge bg-secondary";
  spanCalories.className = "badge bg-secondary"
  spanMinutes.textContent = cookingTime + " minutes";
  spanCalories.textContent = calories + " calories";
  li.append(spanMinutes);
  li.append(spanCalories);
}

// Add to favourites
const addToFavourites = function(id) {
  document.getElementById(id).addEventListener('click', function() {
    let clickedId = parseInt(id);
    for (let recipe of recipes) {
      if (recipe.id === clickedId) {
        newFavRecipe = recipe;
        displayFavouriteRecipes(newFavRecipe)
      }
    }
  })
}

// Display favourites
const displayFavouriteRecipes = function(recipeReceived) {
  let newRecipe = recipeReceived;
  favouriteRecipes = JSON.parse(localStorage.getItem("favRecipes") || "[]");

  let foundRecipe = false;

  if (newRecipe) {
    for (let rec of favouriteRecipes) {
      if (rec.id.toString() === newRecipe.id.toString()) {
        foundRecipe = true;
      }
    }
    if (!foundRecipe) {
      favouriteRecipes.push(newRecipe);
    }
  }

  for (let favRec of favouriteRecipes) {
    if (foundRecipe !== true) {
      let h4 = document.createElement('h4');
      h4.innerHTML = favRec.title;
      document.body.append(h4);
    }
  }
  localStorage.setItem("favRecipes", JSON.stringify(favouriteRecipes));
}

fetchRecipes();
displayFavouriteRecipes();
<!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" />
  <title>Recipes Project V1</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous" />
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css" />
  <link rel="stylesheet" href="recipes-coding-project-v1.css" />
</head>

<body>
  <nav class="navbar navbar-expand-lg navbar-dark">
    <div class="container container-fluid">
      <a class="navbar-brand" href="#">
        <i class="bi bi-egg-fried"></i> Recipes
      </a>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
      <div class="collapse navbar-collapse" id="navbarText">
        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <a class="nav-link" href="#">Recipe List</a>
          </li>
        </ul>
        <span class="navbar-text">
            Favourite Recipes
            <i class="bi bi-heart-fill empty-heart"></i>
          </span>
      </div>
    </div>
  </nav>

  <main class="container">
    <h1>Recipes</h1>
    <ul class="list-group list-group-flush"></ul>

    <h2>Favourite Recipes</h2>
    <ul class="list-group list-group-flush favourite-recipes-ul"></ul>
  </main>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
  <script src="recipes-coding-project-v1.js"></script>
</body>

</html>

Goal: I want to add only once an obj(recipe) when I click a picture, save it to an array in localStorage and create an h4 in the view for each recipe from my localStorage array.

Problem: My problem is that just the first time when I create a new recipe and display it in the view it's duplicated only in the view but no in my array in local storage (there works as expected). However, when I refresh, the view reflects exactly my array as it should and not the duplicate. There is no error or anything, I changed my code a few times but more damage I made, it duplicates every recipe.

My Html:

<main class="container">
      <h1>Recipes</h1>
      <ul class="list-group list-group-flush"></ul>

      <h2>Favourite Recipes</h2>
      <ul class="list-group list-group-flush favourite-recipes-ul"></ul>
</main>

This is my code:

// Add to favourites
const addToFavourites = function (id) {
    document.getElementById(id).addEventListener('click', function() {
       let clickedId = parseInt(id);
       for (let recipe of recipes) { 
           if(recipe.id === clickedId) {
            newFavRecipe = recipe;
            displayFavouriteRecipes(newFavRecipe)
        }
      }
    })
}

// Display favourites
const displayFavouriteRecipes = function (recipeReceived) {
    let newRecipe = recipeReceived;
    favouriteRecipes = JSON.parse(localStorage.getItem("favRecipes") || "[]");
    
let foundRecipe = false;

if (newRecipe) {
    for (let rec of favouriteRecipes) {
        if (rec.id.toString() === newRecipe.id.toString()) {
            foundRecipe = true;
        }
    }
    if (!foundRecipe) {
        favouriteRecipes.push(newRecipe);
    }
}

for (let favRec of favouriteRecipes) {
    if (foundRecipe !== true) {
        let h4 = document.createElement('h4');
        h4.innerHTML = favRec.title;
        document.body.append(h4);
    }
}
localStorage.setItem("favRecipes", JSON.stringify(favouriteRecipes));
}

fetchRecipes();
displayFavouriteRecipes();

Solution

  • I rewrite a lot of codes. I separate the logic to fetch data from API and create Element.

    It should work now.

    const createNewRecipe = (recipes, indivRecipes) => {
      let ul = document.querySelector(".list-group");
      for (let i = 0; i < recipes.length; i++) {
        let li = document.createElement("li");
        li.textContent = recipes[i].title;
        li.id = recipes[i].id;
        li.setAttribute("title", recipes[i].title);
        li.className = "list-group-item";
    
        let img = document.createElement("img");
        img.src = recipes[i].image;
        img.className = "card-img-top";
    
        li.append(img);
        ul.append(li);
        createRecipeDetails(indivRecipes[i], li);
      }
    };
    
    const createRecipeDetails = function (individualRecipeData, li) {
      let cookingTime = individualRecipeData.data.readyInMinutes;
      let calories;
      for (let nutrient of individualRecipeData.data.nutrition.nutrients) {
        if (nutrient.name === "Calories") {
          calories = nutrient.amount;
        }
      }
      let spanMinutes = document.createElement("span");
      let spanCalories = document.createElement("span");
      spanMinutes.className = "badge bg-secondary";
      spanCalories.className = "badge bg-secondary";
      spanMinutes.textContent = cookingTime + " minutes";
      spanCalories.textContent = calories + " calories";
      li.append(spanMinutes);
      li.append(spanCalories);
    };
    
    //Click Event
    document
      .querySelector("ul.list-group.list-group-flush")
      .addEventListener("click", addFavoriteRecipes);
    
    function addFavoriteRecipes(event) {
      const li = event.target.closest("li");
      if (li) {
        const recipeId = li.id;
        const favoriteRecipes = JSON.parse(
          localStorage.getItem("favRecipes") || "[]"
        );
    
        const checkIndex = favoriteRecipes.findIndex(el => el.id === recipeId);
        if (checkIndex === -1) {
          const ul = document.querySelector("ul.favourite-recipes-ul");
          const h4 = document.createElement("h4");
          const title = li.getAttribute("title");
          h4.innerHTML = title;
          ul.append(h4);
          favoriteRecipes.push({ id: li.id, title: title });
          localStorage.setItem("favRecipes", JSON.stringify(favoriteRecipes));
        }
      }
    }
    
    const fetchIndivRecipe = async function (recipeId) {
      try {
        const res = await axios.get(
          `https://api.spoonacular.com/recipes/${recipeId}/information?apiKey=e7221d9e49e04040afd50e7b626e2f88&includeNutrition=true`
        );
        return res;
      } catch (error) {
        console.log(error);
        throw new Error(error);
      }
    };
    
    const fetchRecipes = async function () {
      try {
        const res = await axios.get(
          "https://api.spoonacular.com/recipes/complexSearch?apiKey=e7221d9e49e04040afd50e7b626e2f88&number=2"
        );
        // recipes.push(...res.data.results);
        // console.log({ recipes });
        return res.data.results;
      } catch (error) {
        console.log(error);
      }
    };
    
    const fetchAllIndivRecipes = async recipes => {
      const res = recipes.map(recipe => fetchIndivRecipe(recipe.id));
      return Promise.all(res);
    };
    
    const loadFavorite = () => {
      const favoriteRecipes = JSON.parse(
        localStorage.getItem("favRecipes") || "[]"
      );
    
      const ul = document.querySelector("ul.favourite-recipes-ul");
      for (let favRec of favoriteRecipes) {
        const h4 = document.createElement("h4");
        h4.innerHTML = favRec.title;
        ul.append(h4);
      }
    };
    
    const main = async () => {
      try {
        const recipes = await fetchRecipes();
        const indivRecipes = await fetchAllIndivRecipes(recipes);
        createNewRecipe(recipes, indivRecipes);
        loadFavorite();
      } catch (err) {
        console.log(err);
      }
    };
    
    main();
    <!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" />
        <title>Recipes Project V1</title>
        <link
          href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
          rel="stylesheet"
          integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
          crossorigin="anonymous"
        />
        <link
          rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css"
        />
        <link rel="stylesheet" href="recipes-coding-project-v1.css" />
      </head>
    
      <body>
        <nav class="navbar navbar-expand-lg navbar-dark">
          <div class="container container-fluid">
            <a class="navbar-brand" href="#">
              <i class="bi bi-egg-fried"></i> Recipes
            </a>
            <button
              class="navbar-toggler"
              type="button"
              data-bs-toggle="collapse"
              data-bs-target="#navbarText"
              aria-controls="navbarText"
              aria-expanded="false"
              aria-label="Toggle navigation"
            >
              <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarText">
              <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                <li class="nav-item">
                  <a class="nav-link" href="#">Recipe List</a>
                </li>
              </ul>
              <span class="navbar-text">
                Favourite Recipes
                <i class="bi bi-heart-fill empty-heart"></i>
              </span>
            </div>
          </div>
        </nav>
    
        <main class="container">
          <h1>Recipes</h1>
          <ul class="list-group list-group-flush"></ul>
    
          <h2>Favourite Recipes</h2>
          <ul class="list-group list-group-flush favourite-recipes-ul"></ul>
        </main>
        <script
          src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
          integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
          crossorigin="anonymous"
        ></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
        <script src="recipes-coding-project-v1.js"></script>
      </body>
    </html>