Search code examples
javascriptnunjuckseleventy

How to check in Nunjucks if an array contains another array value with Eleventy


So I have some books data coming from a JSON API. For some of the items in the API, I have created a page in 11ty. I’m trying to list out the items from the API, and check if each item’s slug field matches a fileSlug in my local pages collection. If so, then I can output a link to the content page; otherwise, just output the book’s title from the API.

Here’s what I have in Nunjucks:

{# Loop through book entries in API #}
{% for apiBook in booksApi %}
  {{ apiBook.title }}: 
  {# Loop through my local book pages collection #}
  {% for localBook in collections.books %}
    {# Check if any slugs in the local collection match slugs in API #}
    {% if localBook.fileSlug == apiBook.slug %}
      Item exists locally.
    {% else %}
      Item only exists on API.
    {% endif %}
  {% endfor %}
  <br>
{% endfor %}

What I don't understand, is that this returns twice for every item in the API... for items that don't have a corresponding page in 11ty, it returns Item only exists on API. Item only exists on API.. For items that do have a corresponding page, it returns Item exists locally. Item only exists on API..

Any ideas why it would always return true for the else statement?


Creating a custom filter in .eleventy.js:

eleventyConfig.addFilter("matchLocalBook", (apiBook, localBooks) => {
  return localBooks.find(element => element.fileSlug === apiBook.slug);
});

Solution

  • The problem is the nested loop. You have one loop for the API books and another one inside that for every local book, so your code is running through all local books for every book from the API. This is why you're seeing additional output, the total number of lines will be the count of API books multiplied by the count of local books (e.g. 2 * 2 = 4, or 2 * 3 = 6).

    What you want to do is not trivial in Nunjucks. Normally, you could use the in keyword to check if a book is part of your collection, but that doesn't work when you need to compare object properties.

    Instead, I would recommend moving your logic to JavaScript, where you can do that comparison much easier using Array.prototype.find and similar methods. For example, does your apiBooks variable come from a data file? In that case, you could parse the list there and add the reference to the local book file, if available. Another method would be to add a custom filter that takes an apiBook object and the local books collection as parameters and returns the matching book, if one exists.

    // .eleventy.js
    eleventyConfig.addFilter('getLocalBook', (bookData, localBooks) => {
        // find matching book and return it, or return null
    } );
    

    Then you can use it like this:

    {% for apiBook in booksApi %}
      {% set localBook = apiBook | getLocalBook(collections.books) %}
      {% if localBook %}
        Item exists locally.
      {% else %}
        Item only exists on API.
      {% endif %}
    {% endfor %}