I'm trying to create <optgroup>
s that have subcategories pulled from an API. Currently, only the <optgroup>
s are populating, but not the subcategories.
let select = document.querySelector("#category");
const categoriesURL = "INSERT_URL"
let str = "";
fetch(categoriesURL)
.then(response => response.json())
.then(data => {
data.forEach(category => {
let categoryTitle = category.title;
str += `<optgroup label=${categoryTitle}></optgroup>`
category.subcategories.forEach(sub => {
let subcategories_id = sub.subcategories_id;
let subcategoriesURL = `INSERT_URL/${subcategories_id}`;
fetch(subcategoriesURL)
.then(response => response.json())
.then(subData => {
str += `<option value=${sub.subcategories_id}>` + subData.title + "</option>";
})
})
})
select.innerHTML = "<option disabled selected>Select a category</option>" + str;
});
When defining your optgroup
in the string, you immediately open and close the optgroup, effectively putting every option after the optgroup.
str += `<optgroup label=${categoryTitle}></optgroup>`
Leave the optgroup
open in the aforementioned string.
However, the hard part is closing the string after all the fetch
requests on the subcategories are complete. We can accomplish this with Promise.all
and Array#map
.
Loop over all the subcategories with map
and return Promise
that fetch
returns in the loop. When all the fetch
requests are done the code will continue to the then
block that follows the Promise.all
function. In that block you will have the ids and titles of all your subcategories collected in an array. Loop over array to append to your string.
After the loop, close the optgroup
element.
fetch(categoriesURL)
.then(response => response.json())
.then(data => {
data.forEach(category => {
let categoryTitle = category.title;
str += `<optgroup label=${categoryTitle}>`;
Promise.all(category.subcategories.map(sub => {
let subcategories_id = sub.subcategories_id;
let subcategoriesURL = `INSERT_URL/${subcategories_id}`;
return fetch(subcategoriesURL)
.then(response => response.json())
.then(({ title }) => ({
title,
id: subcategories_id
}))
})).then(subData => {
subData.forEach(({ id, title }) => {
str += `<option value=${id}>${title}</option>`;
})
str += '</optgroup>';
});
})
select.innerHTML = "<option disabled selected>Select a category</option>" + str;
});
Though, overall this a very expensive script as it will create a lot of requests. If you have any say over the backend, then I'd advice that you send the all the data back in a single request and create your list based on that result.