Last active
January 24, 2026 18:00
-
-
Save marmay/13854bba177d853545a038145bdc84e4 to your computer and use it in GitHub Desktop.
miso-bug-report-2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| commit 696f38022202ccdca597b439988144560538032c | |
| Author: Markus Mayr <[email protected]> | |
| Date: Sat Jan 24 18:17:18 2026 +0100 | |
| Fix component lifecycle bugs causing leaks and event issues | |
| Four fixes for component lifecycle management: | |
| 1. Copy componentId when VComp keys match (dom.ts) | |
| When diffing two VComps with matching keys, the componentId was not | |
| being copied from the old tree to the new tree. This caused unmount | |
| callbacks to receive undefined componentId, preventing proper cleanup. | |
| 2. Use destroy() instead of removeChild() in syncChildren (dom.ts) | |
| When removing excess old children in the keyed children sync algorithm, | |
| removeChild() was used which only removes the DOM element. This should | |
| be destroy() to properly trigger unmount callbacks and cleanup. | |
| 3. Update parent VComp.child reference after component re-render (Runtime.hs) | |
| After a component re-renders, the parent VComp's .child reference | |
| still pointed to the old VTree. This caused event delegation to fail | |
| because it traverses the VTree looking for event handlers, but the | |
| stale reference meant new handlers weren't found. | |
| 4. Add base case for empty queue in drain function (Runtime.hs) | |
| The drain function recursively processes the action queue but had no | |
| base case for when the queue is empty, causing infinite recursion. | |
| diff --git a/src/Miso/Runtime.hs b/src/Miso/Runtime.hs | |
| index 8933582..bcf9c98 100644 | |
| --- a/src/Miso/Runtime.hs | |
| +++ b/src/Miso/Runtime.hs | |
| @@ -185,6 +185,7 @@ initialize events _componentParentId hydrate isRoot comp@Component {..} getCompo | |
| _timestamp :: Double <- takeMVar frame | |
| Diff.diff (Just oldVTree) (Just newVTree) _componentDOMRef | |
| liftIO (atomicWriteIORef _componentVTree newVTree) | |
| + FFI.updateRef oldVTree newVTree | |
| let _componentApplyActions = \actions model_ -> do | |
| let info = ComponentInfo _componentId _componentParentId _componentDOMRef | |
| @@ -915,6 +916,7 @@ drain | |
| -> IO () | |
| drain cs@ComponentState {..} = do | |
| drainQueueAt _componentId >>= \case | |
| + [] -> pure () -- Base case: queue empty, stop recursion | |
| actions -> do | |
| case _componentApplyActions actions _componentModel of | |
| (updated, schedules :: [Schedule action]) -> do | |
| diff --git a/ts/miso/dom.ts b/ts/miso/dom.ts | |
| index c5c2be2..69dd130 100644 | |
| --- a/ts/miso/dom.ts | |
| +++ b/ts/miso/dom.ts | |
| @@ -11,6 +11,7 @@ export function diff<T>(c: VTree<T>, n: VTree<T>, parent: T, context: DrawingCon | |
| else if (c.type === VTreeType.VComp && n.type === VTreeType.VComp) { | |
| if (n.key === c.key) { | |
| n.child = c.child; | |
| + n.componentId = c.componentId; | |
| if (c.child) c.child.parent = n; | |
| return; | |
| } | |
| @@ -430,7 +431,7 @@ function syncChildren<T>(os: Array<VTree<T>>, ns: Array<VTree<T>>, parent: T, co | |
| else if (newFirstIndex > newLastIndex) { | |
| tmp = oldLastIndex; | |
| while (oldLastIndex >= oldFirstIndex) { | |
| - removeChild(parent, os[oldLastIndex--], context); | |
| + destroy(os[oldLastIndex--], parent, context); | |
| } | |
| os.splice(oldFirstIndex, tmp - oldFirstIndex + 1); | |
| break; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment