1

Can anybody could me explain how I can avoid duplicate my categories? - I'm new in js/API things.

enter image description here

dom-utils.js:

const createCategorySelectOptions = (expense) =>{
    const selectOption = document.createElement("option");
    selectOption.text = expense.category;
    selectOption.value = expense.category; 
    return selectOption;
}

const createCategorySelectElement = (expenses) =>{
    const selectElement = document.createElement("select");
    expenses.forEach((expense) => {
        selectElement.appendChild(createCategorySelectOptions(expense)); 
    });
    
    return selectElement;

}

export const renderExpensesList = (expenses) => {

    const categorySelector = document.querySelector("#category");
    categorySelector.appendChild(createCategorySelectElement(expenses));
    
}

html:

<label id="category"><span>Category</span> </label>

app.js:

import { renderExpensesList } from './dom-utils.js';
let expenses;

const API_URL = "../expenses.json";
fetch(API_URL).then((response) => response.json()).then((data) =>{
expenses = data.map(expense =>{
    return {
        name: expense.name,
        cost: expense.cost,
        currency: expense.currency,
        category: expense.category,
        date: expense.date,
    };
    
});

renderExpensesList(expenses);
})
.catch((err) =>{
});

JSON:

[
  {
    "name": "cofee",
    "cost": "40",
    "currency": "PLN",
    "category": "food",
    "date": "2022-07-16T00:00:00"
  },
  {
    "name": "bread",
    "cost": "8",
    "currency": "PLN",
    "category": "food",
    "date": "2022-08-03T00:00:00"
  },
  {
    "name": "cheese",
    "cost": "21",
    "currency": "PLN",
    "category": "food",
    "date": "2022-08-03T00:00:00"
  },
  {
    "name": "fuel",
    "cost": "320",
    "currency": "PLN",
    "category": "car",
    "date": "2022-08-14T00:00:00"
  },
  {
    "name": "bread",
    "cost": "8",
    "currency": "PLN",
    "category": "food",
    "date": "2022-09-01T00:00:00"
  },
  {
    "name": "ham",
    "cost": "14",
    "currency": "PLN",
    "category": "food",
    "date": "2022-09-01T00:00:00"
  },
  {
    "name": "butter",
    "cost": "6",
    "currency": "PLN",
    "category": "food",
    "date": "2022-09-10T00:00:00"
  },
  {
    "name": "fuel",
    "cost": "320",
    "currency": "PLN",
    "category": "car",
    "date": "2022-09-10T00:00:00"
  },
  {
    "name": "car wash",
    "cost": "60",
    "currency": "PLN",
    "category": "car",
    "date": "2022-09-10T00:00:00"
  }
]

I'm trying to avoid duplicate my categories. Pure JS. Something like that:

enter image description here

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
A. Holik
  • 13
  • 3

2 Answers2

1

It's a matter of finding unique category names in your data. Also you create unneeded number of extra intermediate variables and functions but in reality you could do it in 1 function. Also adding new DOM elements with pure HTML often less verbose and always faster:

const expenses = [{ "name": "cofee", "cost": "40", "currency": "PLN", "category": "food", "date": "2022-07-16T00:00:00"}, { "name": "bread", "cost": "8", "currency": "PLN", "category": "food", "date": "2022-08-03T00:00:00"}, { "name": "cheese", "cost": "21", "currency": "PLN", "category": "food", "date": "2022-08-03T00:00:00"}, { "name": "fuel", "cost": "320", "currency": "PLN", "category": "car", "date": "2022-08-14T00:00:00"}, { "name": "bread", "cost": "8", "currency": "PLN", "category": "food", "date": "2022-09-01T00:00:00"}, { "name": "ham", "cost": "14", "currency": "PLN", "category": "food", "date": "2022-09-01T00:00:00"}, { "name": "butter", "cost": "6", "currency": "PLN", "category": "food", "date": "2022-09-10T00:00:00"}, { "name": "fuel", "cost": "320", "currency": "PLN", "category": "car", "date": "2022-09-10T00:00:00"}, { "name": "car wash", "cost": "60", "currency": "PLN", "category": "car", "date": "2022-09-10T00:00:00"}];

const renderExpensesList = (expenses) => {

    const selectElement = document.createElement("select");

    selectElement.innerHTML = [...new Set(expenses.map(item => item.category))]
      .map(category =>
        `<option value="${category}">${category}</option>`
      ).join('');
    
    document.querySelector("#category").appendChild(selectElement);
}

renderExpensesList(expenses);
<div id="category"></div>

If you are obsessed with speed:
Collect unique names in an array with Array::reduce() and use Set to check whether a category name is already added to the array:

const expenses = [{ "name": "cofee", "cost": "40", "currency": "PLN", "category": "food", "date": "2022-07-16T00:00:00"}, { "name": "bread", "cost": "8", "currency": "PLN", "category": "food", "date": "2022-08-03T00:00:00"}, { "name": "cheese", "cost": "21", "currency": "PLN", "category": "food", "date": "2022-08-03T00:00:00"}, { "name": "fuel", "cost": "320", "currency": "PLN", "category": "car", "date": "2022-08-14T00:00:00"}, { "name": "bread", "cost": "8", "currency": "PLN", "category": "food", "date": "2022-09-01T00:00:00"}, { "name": "ham", "cost": "14", "currency": "PLN", "category": "food", "date": "2022-09-01T00:00:00"}, { "name": "butter", "cost": "6", "currency": "PLN", "category": "food", "date": "2022-09-10T00:00:00"}, { "name": "fuel", "cost": "320", "currency": "PLN", "category": "car", "date": "2022-09-10T00:00:00"}, { "name": "car wash", "cost": "60", "currency": "PLN", "category": "car", "date": "2022-09-10T00:00:00"}];

const categories = expenses.reduce((acc, item) => {
  const {arr, set} = acc;
  set.has(item.category) || set.add(arr[arr.length] = item.category);
  return acc;
}, {arr:[], set:new Set}).arr;

console.log(categories);
Alexander Nenashev
  • 8,775
  • 2
  • 6
  • 17
  • Thank you, reduce metod worked! Using innerHTML is safe? [DOM manipulationn and danger using innerHHTML](https://betterprogramming.pub/dom-manipulation-the-dangers-of-innerhtml-602f4119d905) – A. Holik Jul 03 '23 at 20:29
  • @A.Holik in my experience `innerHTML` is the fastest way to add DOM elements. about safety i think its' a matter of complexity. here as you see it's quite simple. the only problem could happen if you have `"` inside your category name, then the html will break. you should escape it. i'll add an example later and notify you – Alexander Nenashev Jul 03 '23 at 20:35
  • @A.Holik if the data comes from a user or 3rd party there could XSS attack when injecting JS into HTML. for that some HTML escaping function should be used. most frameworks escape HTML automatically. if the data is prepared by you it's safe – Alexander Nenashev Jul 03 '23 at 20:45
  • Thanks for help, maybe I was trying too hard to avoid using it. Now I'm calmer, I'll try this method next time. – A. Holik Jul 03 '23 at 23:42
0

You can filter your expenses array, turn it into a set, or one of the other options documented in this answer to a similar question:

const expenseSet = new Set(expenses); 
expenseSet.forEach((expense) => {
    selectElement.appendChild(createCategorySelectOptions(expense)); 
});
JimmyB
  • 68
  • 4