Alpine: x-for "items.forEach is not a function" error when looping through an object

Created on 4 Apr 2020  路  9Comments  路  Source: alpinejs/alpine

On Alpine 2.2.5, with this sample data:

function app() {
    return {
        colours: {
            red: '#FF0000',
            green: '#00FF00',
            blue: '#0000FF',
        }
    }
}

and this sample markup:

<div x-data="app()">
    <template x-for="(colour, index) in colours" :key="index">
        <div>
           <span x-text="index"></span>
           <span x-text="colour"></span>
        </div>
    </template>
</div>

I get this error.

alpine.js:420 Uncaught (in promise) TypeError: items.forEach is not a function
    at handleForDirective (alpine.js:420)
    at alpine.js:1504
    at Array.forEach (<anonymous>)
    at Component.resolveBoundAttributes (alpine.js:1459)
    at Component.initializeElement (alpine.js:1396)
    at alpine.js:1377
    at alpine.js:1369
    at walk (alpine.js:85)
    at walk (alpine.js:89)
    at walk (alpine.js:89)

If I drop the object notation for a simple array, the for loop works. Like this:

function app() {
    return {
        colours: [
            '#FF0000',
            '#00FF00',
            '#0000FF'
        ]
    }
}

Any ideas. Am I doing something wrong, overlooking something or does Alpine not like nested objects? Thanks!

Most helpful comment

I've opened quite a simple PR that adds support for looping over objects using the following syntax:

<template x-for="(value, key, index) in items">
</template>

The signature is slightly different to that of the array implementation. For an object:

let data = {
    items: {
        foo: 'bar',
        bob: 'baz'
    }
}

value will become bar when key is foo and index is 0.

All 9 comments

The X-for directive works only with arrays for now (https://github.com/alpinejs/alpine/blob/master/README.md#x-for).

I think it will be supported in the future like in VueJS but in the meantime you can use x-for="(colour, index) in colours.keys()" x-for="(colour, index) in Object.values(colours)" if you need to use an object literal.

Going to look into adding object support today. I actually ran into this issue yesterday too

@SimoTod Just found out that x-for="[key, value] of Object.entries(colors)" works well too.

~Returns all the data I need.~ However Object.entries() needs a polyfill for IE. ( I know, I know)

Turns out I can't access nested objects. Like if I wanted to use nested x-fors

@SimoTod Just found out that x-for="[key, value] of Object.entries(colors)" works well too.

This will also work but is a relatively new ES feature, so might not work across all browsers.

@SimoTod Just found out that x-for="[key, value] of Object.entries(colors)" works well too.

~Returns all the data I need.~ However Object.entries() needs a polyfill for IE. ( I know, I know)

Turns out I can't access nested objects. Like if I wanted to use nested x-fors

Could you create a separate issue for the nested objects problem please?

I've opened quite a simple PR that adds support for looping over objects using the following syntax:

<template x-for="(value, key, index) in items">
</template>

The signature is slightly different to that of the array implementation. For an object:

let data = {
    items: {
        foo: 'bar',
        bob: 'baz'
    }
}

value will become bar when key is foo and index is 0.

@ryangjchandler Slightly off-topic as this isn't directly related to the issue or the PR, but what exactly are the parentheses in the x-for do, what is it called? Like if I wanted to find the docs related to this JavaScript feature, what keywords should I search?

Or is it something internal to Alpine? It sorta reminds me of destructuring assignments, but it isn't.

@ryangjchandler Slightly off-topic as this isn't directly related to the issue or the PR, but what exactly are the parentheses in the x-for do, what is it called? Like if I wanted to find the docs related to this JavaScript feature, what keywords should I search?

Or is it something internal to Alpine? It sorta reminds me of destructuring assignments, but it isn't.

The syntax originates from Angular 1 I belive with the ng-repeat directive. It was then adopted by Vue for use with their v-for directive and since Alpine is heavily inspired by Vue, that syntax has been used here for x-for too.

The brackets themselves act like brackets normally would in a JavaScript expression I guess, generally used to imply an order of precedence to operations and ensure that something is evaluated on it's own, e.g. ! $value === $something and ! ($value === $something). The ! $value might get evaluated before the comparison. Using the brackets helps to ensure the comparison is done before the NOT operation.

It's also easier on the eye. You can easily see where the variable declarations end and the iterable starts. You can think of it like destructuring since that's all it really is:

for (let [value, key] in items) {
    ...
}

Hope this explained it a little bit. You probably knew most of it, but for somebody who happens to read it, I hope it helps explain it. @SimoTod or @calebporzio or @HugoDF might have some better insight, but I've never seen it used outside of a JavaScript framework (excluding other languages like Python of course).

Closing this for now. I'd prefer to not support this extra syntax in the core and push people towards the Object.entries pattern.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

piotrpog picture piotrpog  路  3Comments

dkuku picture dkuku  路  5Comments

ryangjchandler picture ryangjchandler  路  3Comments

BernhardBaumrock picture BernhardBaumrock  路  3Comments

adevade picture adevade  路  3Comments