Skip to content

[BUG] Keyed list rows with value + input two-way binding reference undeclared variables (ReferenceError) #87

Description

@mehmetfiskindal

Describe the bug

Any element inside a keyed .map() row that has both value={...} and an input={...} handler (the standard two-way form binding) generates a keyed-row template that references undeclared variables, throwing ReferenceError at runtime — in the browser and during SSR (renderToString).

Minimal reproduction

// App.tsx
import { Component } from '@geajs/core'

const items = [{ id: 'a', title: 'x' }]

export default class App extends Component {
  template() {
    return (
      <div>
        {items.map((item) => (
          <article key={item.id}>
            <label>
              <span>T</span>
              <input value={item.title} input={(e: Event) => console.log(e)} />
            </label>
          </article>
        ))}
      </div>
    )
  }
}

Render it (client render or SSR). The compiled row function is emitted as:

const __ki_L0 = (item, idx, d) => {
  const root = (_tpl1_root || (_tpl1_root = _tpl1_create())).cloneNode(true);
  const el0 = root;
  const evt2 = el1;              // ← el1 was never declared
  evt2.__onct_input = __hm_0;
  {
    root.firstChild.childNodes[1].value = item.title;
  }
  root[GEA_DOM_ITEM] = item;
  return root;
};

ReferenceError: el1 is not defined

Triggering patterns (all confirmed):

Variant Result
input with value + input handler BROKEN
input with value only ok
input with input handler only ok
button + input(value+input) BROKEN

Root cause

In the keyed-list patch-plan optimizer (collectPatchRowPlanbuildPatchWritepruneExtractedCreateItemStatements):

  1. When an input element has both a value slot and an input event slot on the same walk, emitWalkCapture declares the value slot as elN and emits the event var as an alias: const evtM = elN;
  2. The optimizer then converts reactiveValueRead(elN, ...) into an inline one-shot write and adds elN to plan.deadBindings.
  3. The prune step removes const elN = ... but does not check that the surviving alias declaration const evtM = elN; still references it.

Suggested fix: when adding names to deadBindings, skip names that are aliased by other surviving declarations (or rewrite aliases to raw walks before pruning).

Affected versions

Reproduced with @geajs/vite-plugin@1.4.1 and also @geajs/vite-plugin@1.2.3, with @geajs/core@1.4.0.

Impact

Every keyed list row using the common value + input two-way pattern crashes as soon as the row renders. This blocks both client rendering and SSR for any form-heavy app.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions