Search code examples
javascriptnunjuckseleventy

How to get unique nested array items in Nunjucks (11ty) and loop over it?


I have data from headless CMS of the following type:

type Assortiment = {
  list: Array<Item>
  // ...
}

type Item = {
  brands: Array<Brand>
  // ...
}

type Brand = {
  logo: string,
  title: string,
}

As you can see, there is a list of assortiment entries each with it's own brands list. I need to loop over all unique brands and show them on page using nunjucks.

I tried writing a custom filter for that but couldn't get it working in nunjucks loop. JS would be like that:

eleventyConfig.addFilter('uniqueBrands', (assortiment) => {
  const brands = assortiment.list.flatMap(item => item.brands)
  const map = new Map(brands.map(b => [b.logo, b.title]))

  return [...map.entries()]
})

How I want to use it:

<ul>
  <!-- not sure if I apply a filter correctly below... -->
  {% for logo, title in (assortiment.list | uniqueBrands) %}
    <li>
      <img src="{{logo}}" alt="{{title}}" title="{{title}}" />
    </li>
  {% endfor %}
</ul>

My tech stack is 11ty + Nunjucks + Netlify CMS.
It seems like I'm trying to misuse nunjucks filter, if so how can it be done otherwise?


Got it working, final solution:

// filters
cfg.addFilter('flatMap', (list, key) => list.flatMap((x) => x[key]))

cfg.addFilter('unique', (list, key) => {
  const map = new Map(list.map((x) => [x[key], x]))

  return [...map.values()]
})

// render
<ul>
  {% for brand in (assortiment.list | flatMap('brands') | unique('logo')) %}
  <li>
    <img src="{{brand.logo}}" alt="{{brand.title}}" />
  </li>
  {% endfor %}
</ul>

Solution

  • Not sure what you are missing, your code looks fine. You apply a filter in Nunjucks using the pipe operator:

    <ul>
      {% for logo, title in (assortiment | uniqueBrands) %}
        <li>
          <img src="{{ logo }}" alt="{{ title }}" title="{{ title }}">
        </li>
      {% endfor %}
    </ul>
    

    It seems like I'm trying to misuse nunjucks filter, if so how can it be done otherwise?

    It would probably be easier to create a collection with your unique brands. Your JS code could stay almost the same, but you'd need to get the list of brands using the collection API.