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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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