'Svelte - is there a way to use JS inside #each?

I'm iterating over an array of data and I want to do some processing on it before rendering it.

I know that I could create a new component and pass array entry to i, and do the processing within that sub-component, or I could add a helper function getClass(entry) or I could inline a tenary operator,

but I'm wondering if there's a way to do something like this, to inline some code into the each block. Non-functional example:



<div class="Menu">
  {#each menuEntries as entry, i }
    {{ 
      let classes = entry.classes;
      
      if (entry.submenu) {
        classes += ' has-submenu';
      }
     }}
      
      <div class="menu-entry {classes}">...</div>
  {/each}
</div>


Edit:

It seems like a workaround like this works. The only thing is that classes have to be defined before the loop.

<script>
let classes = '';
</script>
<div class="Menu">
  {#each menuEntries as entry, i }
    {(() => {
      classes = entry.classes;
      
      if (entry.submenu) {
        classes += ' has-submenu';
      }
      return ''; // return empty string so Svelte does not print it
     })()}
      
      <div class="menu-entry {classes}">...</div>
  {/each}
</div>




Solution 1:[1]

You can use an Array.map function to do some additional processing.

This way you can add an optional argument using the map "this" and use additional loop variables using [....] returned from the map function.

Example:

{#each menuEntries.map(extraProcessing, thisArg) as [entry, arg2, arg3] , i }

  ... loop using entry, arg2, arg3, i

{/each} 

Example extraProcessing function:

function extraProcessing(entry, idx) {

   ... do something using: entry, idx and this (thisArg)

return [entry, arg2, arg3]

And a here a REPL with your example.

Solution 2:[2]

For those who need to do some calculations inside the #each block you can use @const statement.

<script>
  const items = ['a', 'b', 'c'];
</script>

{#each items as item}
  {@const exclamationMark = item + '!'}

  <p>{exclamationMark}</p>
{/each}

Solution 3:[3]

if all you want is activate a class if entry has a submenu property you could use a conditional class this

<style>
    .has-submenu {/* your conditional css */}
</style>

<div class="Menu">
  {#each menuEntries as entry, i }      
      <div
          class={"menu-entry " + entry.classes}
          class:has-submenu={entry.submenu}
      >
          ...
      </div>
  {/each}
</div>

just beware, class:has-submenu={entry.submenu} evaluates to true and activates the class only if entry.submenu is truthy itself (not null, undefined, 0, etc), so if that's a problem you should directly check whether the property is there

Solution 4:[4]

Another solution is to a Svelte component to process data and return results using slots.

you can add a component named Process.svelte which receives args and function, the result of calling the function will be provided in a slot prop.

<script>
    export let args = {}
    export let process
</script>

<slot res={process(args)}/>

import Process.svelte

<script>
    import Process from './Process.svelte'
    let menuEntries = []
</script>

<div class="Menu">
  {#each menuEntries as entry, i }
      <Process process={() => {
      let a= entry.classes;
      if (entry.submenu) {
        a+= ' has-submenu';
      }
      return a; // the return value can be accessed by the children via let:res
     }} let:res>
                <div class="menu-entry {res}">...</div>
       </Process>
  {/each}
</div>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Roman Mahotskyi
Solution 3 skpn
Solution 4 Ryuman