LearnNewsExamplesServices
Frontmatter
id8066
titleStandardize Renderer Component Lifecycle Management
stateClosed
labels
airefactoringarchitecture
assigneestobiu
createdAtDec 9, 2025, 2:48 AM
updatedAtDec 9, 2025, 3:54 AM
githubUrlhttps://github.com/neomjs/neo/issues/8066
authortobiu
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtDec 9, 2025, 3:54 AM

Standardize Renderer Component Lifecycle Management

Closed v11.17.0 airefactoringarchitecture
tobiu
tobiu commented on Dec 9, 2025, 2:48 AM

Refactor the management of child components generated by renderers to ensure consistent lifecycle handling (mounting/destroying) across different contexts (LivePreview vs ContentComponent).

Problem: ContentComponent manually manages the mounted state of Markdown-embedded components (livePreviews, customComponents) to support re-mounting (e.g., navigating between portal tabs). This logic is tightly coupled to the container and not reusable by LivePreview when running in markdown mode.

Solution: Shift the responsibility of managing these "floating" components to the renderer that creates them.

Changes:

  1. src/code/renderer/Markdown.mjs:

    • Add activeComponents array to track created instances.
    • Implement updateComponentState(mounted): Iterates activeComponents and sets their mounted config.
    • Implement destroyComponents(): Destroys all activeComponents and clears the array.
    • Update render() to populate activeComponents instead of returning them.
  2. src/code/renderer/Base.mjs:

    • Define abstract updateComponentState(mounted) (no-op by default).
    • Define abstract destroyComponents() (no-op by default).
  3. src/code/LivePreview.mjs:

    • Remove customComponents and livePreviews.
    • Implement afterSetMounted(value): Call this.renderer?.updateComponentState(value).
    • Update destroyChildInstances: Call this.renderer?.destroyComponents().
  4. apps/portal/view/learn/ContentComponent.mjs:

    • Remove customComponents and livePreviews.
    • Update afterSetMounted: Delegate to this.renderer?.updateComponentState(value).
    • Update destroyChildInstances: Delegate to this.renderer?.destroyComponents().

Goal: Decouple component lifecycle management from the container view, ensuring LivePreview and ContentComponent can both correctly handle re-mounting scenarios for Markdown content.

tobiu added the ai label on Dec 9, 2025, 2:48 AM
tobiu added the refactoring label on Dec 9, 2025, 2:48 AM
tobiu added the architecture label on Dec 9, 2025, 2:48 AM
tobiu assigned to @tobiu on Dec 9, 2025, 3:41 AM
tobiu referenced in commit 8623d74 - "Standardize Renderer Component Lifecycle Management #8066" on Dec 9, 2025, 3:43 AM
tobiu
tobiu Dec 9, 2025, 3:44 AM

Input from Gemini:

✦ I have verified the rationale and implementation of the mounted state handling in ContentComponent and MarkdownRenderer.

Analysis: The regression was caused by the initial mounted setter call during construction (where value is false and oldValue is undefined), which prematurely triggered updateComponentState. Additionally, simply setting component.mounted = true was insufficient for re-mounting components that were removed from the DOM but kept in memory.

Fixes Implemented:

  1. ContentComponent.afterSetMounted: Added if (me.isConstructed) check. This prevents the renderer update from running during the initial config application, ensuring it only responds to actual lifecycle changes (like tab activation/deactivation).
  2. MarkdownRenderer.updateComponentState:
    • For mounted === true: Changed component.mounted = true to component.initVnode(true). This forces a re-initialization of the VNode tree and a fresh mount, ensuring the component correctly re-attaches to the DOM with its current state.
    • For mounted === false: Kept component.mounted = false. Crucially, this does NOT trigger a DOM unmount itself (the parent container handles the DOM removal). This step simply updates the internal state flag of the child component to reflect that it is no longer in the document, ensuring its state is consistent for the next mount cycle.

Conclusion: The changes correctly address the lifecycle synchronization issue. The use of initVnode(true) is the correct approach for re-mounting components that have been detached but preserved in memory, especially in the context of the Portal app's card layout where DOM nodes are removed but JS instances persist.

tobiu closed this issue on Dec 9, 2025, 3:54 AM