skip to Main Content

I have a data structure similar to the below; Simply I list of items, however, some items have nested items. I am calling the objects that have nested objects "sections" for the purposes of this question.

data = [
  {title: "Section 1", items: [{id: "1"}, {id: "2"}]},
  {title: "Section 2", items: [{id: "3"}, {id: "4"}]},
  {id: "5"},
  {id: "6"},
  {id: "7"},
]

I am trying to loop through the items within the "sections" and then the remaining items however, I am getting the above error.

Attempt 1:

function addIsFavourite(favourites = [], sections) {
  for (const section of sections) {
    if ("items" in section) {
      // Section has nested items
      for (const item of section.items) {
        item.isFavourite = favourites.includes(item.id);
      }
    } else {
      // Section holds items directly
      for (const item of section) {
        item.isFavourite = favourites.includes(item.id);
      }
    }
  }
  return sections;
}

Returns error

[TypeError: iterator method is not callable]

Attempt 2:

function addIsFavourite(favourites = [], sections) {
  if ("items" in sections) {
    for (const section of sections) {
      for (const item of section.items) {
        item.isFavourite = favourites.includes(item.id);
      }
    }
  } else {
    for (const item of sections) {
      item.isFavourite = favourites.includes(item.id);
    }
  }
  return sections;
}

This only works on the first "section".

3

Answers


  1. You’re trying to loop through each section object, which only contains an id property and doesn’t have the iterator protocol. You don’t need a loop there as you’re already in the scope of each section.

    Neither do you need the else block as you’d probably want to check if the section itself is in the favourites array at any time.

    function addIsFavourite(favourites = [], sections) {
      for (const section of sections) {
        if ("items" in section) {
          // Section has nested items
          for (const item of section.items) {
            item.isFavourite = favourites.includes(item.id);
          }
        }
    
        section.isFavourite = favourites.includes(section.id);
      }
    
      return sections;
    }
    

    If you want loop through an object and use the iterator protocol, then you’d need to use the Object.entries() static method to convert an object to an array, which can be iterated over with for...of.

    Login or Signup to reply.
  2. The error you’re encountering indicates that you’re trying to iterate over an object that doesn’t have a callable iterator method. In your code, it seems like you’re assuming that section can be both an object and an array, but you need to handle these cases differently.

    Here’s a revised version of your code that checks if section is an array before attempting to iterate over its items:

    const data = [
          { title: "Section 1", items: [{ id: "1" }, { id: "2" }] },
          { title: "Section 2", items: [{ id: "3" }, { id: "4" }] },
          { id: "5" },
          { id: "6" },
          { id: "7" },
        ];
        
        const favourites = ["1", "4", "6"];
        
        function addIsFavourite(favourites = [], sections) {
          for (let section of sections) {
            if ("items" in section) {
              // Section has nested items
              for (let item of section.items) {
                item.isFavourite = favourites.includes(item.id);
              }
            } else if (Array.isArray(section)) {
              // Section holds items directly
              for (let item of section) {
                item.isFavourite = favourites.includes(item.id);
              }
            } else {
              // Section is a standalone item
              section.isFavourite = favourites.includes(section.id);
            }
          }
          return sections;
        }
        
        const result = addIsFavourite(favourites, data);
        
        console.log(result);

    I hope this will work for you.

    Login or Signup to reply.
  3. All you need to do is change the else block to only include the line section.isFavourite = favourites.includes(section.id).

    You can also solve the problem with a one-liner like this:

    const data = [
      {title: "Section 1", items: [{id: "1"}, {id: "2"}]},
      {title: "Section 2", items: [{id: "3"}, {id: "4"}]},
      {id: "5"},
      {id: "6"},
      {id: "7"},
    ]
    
    let favourites = ["1", "6"]
    
    data.flatMap(x => x.items ?? x)
      .forEach(item => favourites.includes(item.id) && (item.isFavourite = true))
    
    console.log(data)

    data.flatMap(x => x.items ?? x) will produce an array of all items at the first and second level (it will break if you have three levels of nesting, and you’d have to use recursion or a stack instead).

    Then, you can simply use forEach to set item.isFavourite where necessary.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search