[GH-ISSUE #432] Bug: conditional sibling causes insertBefore crash in <box> #111

Open
opened 2026-03-02 23:44:37 +03:00 by kerem · 3 comments
Owner

Originally created by @IsraelAraujo70 on GitHub (Dec 19, 2025).
Original GitHub issue: https://github.com/anomalyco/opentui/issues/432

Originally assigned to: @msmps on GitHub.

Summary

When rendering a <textarea> and then conditionally rendering a sibling <line-number> before it (after the textarea ref becomes available), OpenTUI crashes with:
Error: Anchor does not exist
at insertBefore (Renderable.ts:1161:17)
This looks like React trying to insertBefore() into a <box> container, but the anchor resolution fails.

Environment

  • Runtime: Bun
  • @opentui/core: 0.1.62
  • @opentui/react: 0.1.62
  • react: 19.2.3
  • OS: (fill in)

Steps to Reproduce

  1. Render a <textarea> and store its instance via a callback ref.
  2. Conditionally render <line-number target={textareaInstance} /> before the textarea once the ref is set.
    Minimal repro:
const [target, setTarget] = useState<any>(null)
return (
  <box flexDirection="row">
    {target && <line-number target={target} />}
    <textarea ref={setTarget} />
  </box>
)

Expected

mounts and attaches to the textarea without crashing.
Actual
Crash with Error: Anchor does not exist coming from insertBefore.

Notes / Suspected Cause

  • On initial render, only exists.</li> <li>After the ref sets state, React re-renders and attempts to insert <line-number> before an existing child.</li> <li>The <box> host implementation (or its internal children tracking) appears to mishandle insertBefore anchor resolution in this scenario.<br> Workarounds</li> <li>Render <textarea> first (so <line-number> gets appended when it becomes available), and use flexDirection="row-reverse" to keep line numbers on the left.</li> <li>Alternatively, render <line-number> unconditionally and hide it until target exists (avoid insertion entirely).</li> </ul> </body></html>
Originally created by @IsraelAraujo70 on GitHub (Dec 19, 2025). Original GitHub issue: https://github.com/anomalyco/opentui/issues/432 Originally assigned to: @msmps on GitHub. ### Summary When rendering a `<textarea>` and then conditionally rendering a sibling `<line-number>` *before it* (after the textarea ref becomes available), OpenTUI crashes with: Error: Anchor does not exist at insertBefore (Renderable.ts:1161:17) This looks like React trying to `insertBefore()` into a `<box>` container, but the anchor resolution fails. ### Environment - Runtime: Bun - `@opentui/core`: 0.1.62 - `@opentui/react`: 0.1.62 - `react`: 19.2.3 - OS: (fill in) ### Steps to Reproduce 1. Render a `<textarea>` and store its instance via a callback ref. 2. Conditionally render `<line-number target={textareaInstance} />` *before* the textarea once the ref is set. Minimal repro: ```tsx const [target, setTarget] = useState<any>(null) return ( <box flexDirection="row"> {target && <line-number target={target} />} <textarea ref={setTarget} /> </box> ) ``` ### Expected <line-number> mounts and attaches to the textarea without crashing. Actual Crash with Error: Anchor does not exist coming from insertBefore. ### Notes / Suspected Cause - On initial render, only <textarea> exists. - After the ref sets state, React re-renders and attempts to insert <line-number> before an existing child. - The <box> host implementation (or its internal children tracking) appears to mishandle insertBefore anchor resolution in this scenario. Workarounds - Render <textarea> first (so <line-number> gets appended when it becomes available), and use flexDirection="row-reverse" to keep line numbers on the left. - Alternatively, render <line-number> unconditionally and hide it until target exists (avoid insertion entirely).
Author
Owner

@msmps commented on GitHub (Dec 22, 2025):

@IsraelAraujo70 i will look into this but was there a reason you needed to set the target over using parent/child?

import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"

const initialContent = `const results = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

// Calculate the sum
const sum = results.reduce((acc, val) => acc + val, 0)
console.log('Sum:', sum)

// Find even numbers
const evens = results.filter(n => n % 2 === 0)
console.log('Even numbers:', evens)`

export const App = () => {
  return (
    <line-number>
      <textarea focused initialValue={initialContent} />
    </line-number>
  )
}

const renderer = await createCliRenderer()
createRoot(renderer).render(<App />)
Image
<!-- gh-comment-id:3683805460 --> @msmps commented on GitHub (Dec 22, 2025): @IsraelAraujo70 i will look into this but was there a reason you needed to set the target over using parent/child? ```typescript import { createCliRenderer } from "@opentui/core" import { createRoot } from "@opentui/react" const initialContent = `const results = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] // Calculate the sum const sum = results.reduce((acc, val) => acc + val, 0) console.log('Sum:', sum) // Find even numbers const evens = results.filter(n => n % 2 === 0) console.log('Even numbers:', evens)` export const App = () => { return ( <line-number> <textarea focused initialValue={initialContent} /> </line-number> ) } const renderer = await createCliRenderer() createRoot(renderer).render(<App />) ``` <img width="622" height="378" alt="Image" src="https://github.com/user-attachments/assets/8c914154-86bd-495c-9d01-aab84939cd0a" />
Author
Owner

@IsraelAraujo70 commented on GitHub (Dec 26, 2025):

@msmps Thanks for the suggestion! I tried wrapping the <textarea> with <line-number> as you showed:

<line-number fg={colors.comment} bg={colors.background} paddingRight={1}>
  <textarea
    ref={textareaRef}
    key={buffer.id}
    flexGrow={1}
    height={height}
    initialValue={buffer.content}
    focused={focused}
    backgroundColor={colors.background}
    textColor={colors.foreground}
    // ...
  />
</line-number>

However, I'm now getting a different error when switching between files/buffers (when the textarea gets unmounted):

Error: LineNumberRenderable: Cannot remove target directly. Use clearTarget() instead.
    at remove (/node_modules/@opentui/src/renderables/LineNumberRenderable.ts:469:17)
    at removeChild (/node_modules/@opentui/react/index.js:381:18)
    ...

It seems like when React tries to unmount the child , the <line-number> component doesn't handle the removal correctly through the React reconciler.<br> Is there a way to properly handle this? Maybe the React bindings need to call clearTarget() before removing the child?</p> </body></html>

<!-- gh-comment-id:3693127180 --> @IsraelAraujo70 commented on GitHub (Dec 26, 2025): @msmps Thanks for the suggestion! I tried wrapping the `<textarea>` with `<line-number>` as you showed: ``` <line-number fg={colors.comment} bg={colors.background} paddingRight={1}> <textarea ref={textareaRef} key={buffer.id} flexGrow={1} height={height} initialValue={buffer.content} focused={focused} backgroundColor={colors.background} textColor={colors.foreground} // ... /> </line-number> ``` However, I'm now getting a different error when switching between files/buffers (when the textarea gets unmounted): ``` Error: LineNumberRenderable: Cannot remove target directly. Use clearTarget() instead. at remove (/node_modules/@opentui/src/renderables/LineNumberRenderable.ts:469:17) at removeChild (/node_modules/@opentui/react/index.js:381:18) ... ``` It seems like when React tries to unmount the child <textarea>, the <line-number> component doesn't handle the removal correctly through the React reconciler. Is there a way to properly handle this? Maybe the React bindings need to call clearTarget() before removing the child?
Author
Owner

@kommander commented on GitHub (Dec 27, 2025):

Mhh to support react with the LineNumberRenderable properly, it should probably handle clearTarget internally when the target is removed. Would be more aligned with other APIs in the core.

<!-- gh-comment-id:3694157987 --> @kommander commented on GitHub (Dec 27, 2025): Mhh to support react with the LineNumberRenderable properly, it should probably handle `clearTarget` internally when the target is removed. Would be more aligned with other APIs in the core.
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#111
No description provided.