Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions src/lib/codegenerator/generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -612,18 +612,27 @@ const generateForLoopCode = function (templateObject, parent) {
effectKey = effectKey.match(/[^.]+$/)[0]
}

// get the reference to range from and to
const effectKeysRegex = /\$([^,} ]+)/g
const effectKeys = [...range.matchAll(effectKeysRegex)].map((match) => `'${match[1]}'`)
const collectionExpr = result[2].trim()
const propName = collectionExpr.replace(/^\$/, '').split('.')[0]

ctx.renderCode.push(`
let eff${forStartCounter} = () => {
forloops[${forStartCounter}](${cast(result[2], ':for')}, elms, created[${forStartCounter}])
// Access underlying prop/state if this is a direct property to ensure tracking
if (component[Symbol.for('propKeys')] && component[Symbol.for('propKeys')].indexOf('${propName}') !== -1) {
void component[Symbol.for('props')]['${propName}']
} else if (component[Symbol.for('stateKeys')] && component[Symbol.for('stateKeys')].indexOf('${propName}') !== -1) {
void component[Symbol.for('state')]['${propName}']
}

const collection = ${cast(result[2], ':for')}
forloops[${forStartCounter}](collection, elms, created[${forStartCounter}])
}

component[Symbol.for('effects')].push(eff${forStartCounter})

effect(eff${forStartCounter}, ['${effectKey}', ${effectKeys.join(',')}])
// Use null as currentKey to track all property accesses during effect execution.
// This ensures computed property dependencies are tracked when accessing underlying reactive properties.
effect(eff${forStartCounter}, null)
`)

ctx.cleanupCode.push(`
Expand Down
57 changes: 50 additions & 7 deletions src/lib/codegenerator/generator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3284,7 +3284,8 @@ test('Generate code for a template with a simple for-loop on an Element', (asser
}

let eff1 = () => {
forloops[1](component.items, elms, created[1])
const collection = component.items
forloops[1](collection, elms, created[1])
}

component[Symbol.for('effects')].push(eff1)
Expand Down Expand Up @@ -3439,7 +3440,8 @@ test('Generate code for a template with a simple for-loop on an Element, Using d
}

let eff1 = () => {
forloops[1](component.$appState.list, elms, created[1])
const collection = component.$appState.list
forloops[1](collection, elms, created[1])
}

component[Symbol.for('effects')].push(eff1)
Expand Down Expand Up @@ -3594,7 +3596,8 @@ test('Generate code for a template with a simple for-loop on an Element, Using d
}

let eff1 = () => {
forloops[1](component.content.data, elms, created[1])
const collection = component.content.data
forloops[1](collection, elms, created[1])
}

component[Symbol.for('effects')].push(eff1)
Expand Down Expand Up @@ -3747,7 +3750,8 @@ test('Generate code for a template with a simple for-loop on an Element with a c
}

let eff1 = () => {
forloops[1](component.items, elms, created[1])
const collection = component.items
forloops[1](collection, elms, created[1])
}

component[Symbol.for('effects')].push(eff1)
Expand Down Expand Up @@ -3900,7 +3904,8 @@ test('Generate code for a template with a simple for-loop on an Element with a k
}

let eff1 = () => {
forloops[1](component.items, elms, created[1])
const collection = component.items
forloops[1](collection, elms, created[1])
}

component[Symbol.for('effects')].push(eff1)
Expand Down Expand Up @@ -4090,7 +4095,8 @@ test('Generate code for a template with a simple for-loop on a Component with a
}

let eff1 = () => {
forloops[1](component.items, elms, created[1])
const collection = component.items
forloops[1](collection, elms, created[1])
}

component[Symbol.for('effects')].push(eff1)
Expand Down Expand Up @@ -4254,7 +4260,8 @@ test('Generate code for a template with a simple for-loop on an Element with an
}

let eff1 = () => {
forloops[1](component.items, elms, created[1])
const collection = component.items
forloops[1](collection, elms, created[1])
}

component[Symbol.for('effects')].push(eff1)
Expand Down Expand Up @@ -4307,6 +4314,42 @@ test('Generate code for a template with a simple for-loop on an Element with an
assert.end()
})

test('Generate code for for-loop with empty array that gets populated - verifies reactivity fix', (assert) => {
const templateObject = {
children: [
{
[Symbol.for('componentType')]: 'Element',
children: [
{
[Symbol.for('componentType')]: 'Element',
':for': 'item in $items',
},
],
},
],
}

const generated = generator.call(scope, templateObject)
const renderCode = generated.render.toString()

assert.ok(
renderCode.includes('const collection = component.items'),
'Effect should store array in variable to ensure reactive tracking when array changes from empty to populated'
)

assert.ok(
renderCode.includes('effect(eff1') && renderCode.includes("'items'"),
'Effect should be registered with items key for tracking'
)

assert.ok(
renderCode.includes('forloops[1](collection, elms, created[1])'),
'Forloop should use the collection variable to ensure reactive property access is tracked'
)

assert.end()
})

test('Generate code for a template with double $$ (i.e. referencing a Blits plugin)', (assert) => {
const templateObject = {
children: [
Expand Down