Skip to content

JSX: foreach on a jsx.render-built template silently renders nothing (reactive children work) #401

Description

@brianmhunt

Drafted by Claude (agent) on behalf of @brianmhunt.

Summary

Applying the foreach binding to a template built by tko.jsx.render(...) renders zero items and emits no warning. The same ko-foreach markup parsed from innerHTML renders correctly, and the reactive-children form ({ko.computed(() => arr().map(...))}) works. The silent 0-render is an easy multi-hour trap.

Environment

  • @tko/build.reference 4.1.0 (https://tko.io/lib/tko.js), browser, no build step (esbuild / Bun transpile with jsxFactory: 'tko.jsx.createElement', jsxFragment: 'tko.jsx.Fragment').

Repro

Paste-run on any page that loads tko.js:

const tko = globalThis.tko, ko = tko;

// A) foreach + JSX-built template → 0 li (unexpected, no warning)
const a = ko.observableArray(['x', 'y', 'z']);
const { node: nodeA } = tko.jsx.render(
  tko.jsx.createElement('ul', { 'ko-foreach': 'a' },
    tko.jsx.createElement('li', { 'ko-text': '$data' })));
document.body.appendChild(nodeA);
ko.applyBindings({ a }, nodeA);
console.log('A jsx foreach:', nodeA.querySelectorAll('li').length);        // 0

// B) identical ko-foreach markup via innerHTML → 3 li (works)
const b = ko.observableArray(['x', 'y', 'z']);
const ul = document.createElement('ul');
ul.setAttribute('ko-foreach', 'b');
ul.innerHTML = '<li ko-text="$data"></li>';
document.body.appendChild(ul);
ko.applyBindings({ b }, ul);
console.log('B innerHTML foreach:', ul.querySelectorAll('li').length);     // 3

// C) reactive children (JSX-native list pattern) → 3 li (works, live)
const c = ko.observableArray(['x', 'y', 'z']);
const { node: nodeC } = tko.jsx.render(
  tko.jsx.createElement('ul', {}, ko.computed(() => c().map(v => tko.jsx.createElement('li', {}, v)))));
document.body.appendChild(nodeC);
console.log('C reactive children:', nodeC.querySelectorAll('li').length);  // 3

Expected

Either (A) behaves like (B)/(C), or foreach warns that its template is a JSX-built node it can't clone — rather than silently rendering nothing.

Why it bites

agents/guide.md presents

<ul ko-foreach={items}><li ko-text="$data" /></ul>

as a JSX example, which implies (A) is supported. But the verified behaviors for @tko/utils.jsx describe reactive children / observable-array diffs as the JSX list path — not foreach + child template. So the guide example steers JSX users straight into the silent-failure path. (Cost me a long detour before reactive children resolved it cleanly.)

Suggested fixes (cheapest first)

  1. Docs — replace/relabel the ko-foreach + template JSX example in agents/guide.md with the reactive-children form, or explicitly mark foreach + template as data-bind/HTML-only.
  2. Dev-mode warning — have foreach (and template bindings) warn when handed a JSX-origin template it renders empty / can't clone. TKO already throws on double-binding non-JSX nodes (@tko/utils.jsx verified behaviors) — same diagnostic spirit.
  3. (Optional, larger) make foreach clone JSX-built templates (carry the native-provider accessors into the clones) so the guide example works as written.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions