[GH-ISSUE #214] Calling remove on a TextNodeRenderable in react throws with "Child not found in children" error #819

Closed
opened 2026-03-14 08:41:00 +03:00 by kerem · 0 comments
Owner

Originally created by @eli0shin on GitHub (Oct 12, 2025).
Original GitHub issue: https://github.com/anomalyco/opentui/issues/214

There is an architectural mismatch in the class hierarchy:

  1. BaseRenderable declares abstract remove(id: string): void - expecting ID-based lookup
  2. Renderable (which most components inherit from) implements this correctly:
    • Uses renderableMapById.get(id) to look up child by ID
    • Properly removes it from layout and internal data structures
  3. TextNodeRenderable (which SpanRenderable inherits from) implements it incorrectly:
    • Signature: remove(child: string | TextNodeRenderable): this
    • Uses indexOf(child) to find the child - NO ID LOOKUP
    • Expects a direct child reference or string literal

For example, this code works:

<box>
  {
    showTextA ?
      <text><span>text a</span></text> :
      <text><span>text b</span></text>
  }
</box>

But this code throws the error because the child of <text> is conditional

<box>
   <text>
    {
      showTextA ?
        <span>text a</span> :
        <span>text b</span>
    }
  </text>
</box>

The solid reconciler passes node reference directly for TextNodeRenderables and passes it to remove but the react one does not, which raises the question: Why are TextNodeRenderables different than other renderables? Why do they use remove by reference instead of by id?

It seems that the correct fix here would be to align TextNodeRenderables with other renderables so that they use remove by id as well. Alternatively we can add the same lookup by id reference in the react reconciler.

Originally created by @eli0shin on GitHub (Oct 12, 2025). Original GitHub issue: https://github.com/anomalyco/opentui/issues/214 There is an architectural mismatch in the class hierarchy: 1. **BaseRenderable** declares `abstract remove(id: string): void` - expecting ID-based lookup 2. **Renderable** (which most components inherit from) implements this correctly: - Uses `renderableMapById.get(id)` to look up child by ID - Properly removes it from layout and internal data structures 3. **TextNodeRenderable** (which `SpanRenderable` inherits from) implements it incorrectly: - Signature: `remove(child: string | TextNodeRenderable): this` - Uses `indexOf(child)` to find the child - NO ID LOOKUP - Expects a direct child reference or string literal For example, this code works: ```javascript <box> { showTextA ? <text><span>text a</span></text> : <text><span>text b</span></text> } </box> ``` But this code throws the error because the child of `<text>` is conditional ```javascript <box> <text> { showTextA ? <span>text a</span> : <span>text b</span> } </text> </box> ``` The solid reconciler [passes node reference directly for TextNodeRenderables](https://github.com/sst/opentui/blob/7e53ea49e6f65040c0673b2fb8682b9602ddd1e9/packages/solid/src/reconciler.ts#L121) and passes it to `remove` [but the react one does not](https://github.com/sst/opentui/blob/7e53ea49e6f65040c0673b2fb8682b9602ddd1e9/packages/react/src/reconciler/host-config.ts#L59-L61), which raises the question: Why are `TextNodeRenderable`s different than other renderables? Why do they use remove by reference instead of by id? It seems that the correct fix here would be to align `TextNodeRenderable`s with other renderables so that they use remove by id as well. Alternatively we can add the same lookup by id reference in the react reconciler.
kerem closed this issue 2026-03-14 08:41:05 +03:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/opentui#819
No description provided.