Mithril.js: Uncaught TypeError & DOMException when rerendering

Created on 10 Mar 2018  路  7Comments  路  Source: MithrilJS/mithril.js

This is a super bizarre error. Seems like it happens semi randomly. I was able to narrow it down to a reproducible example. Hopefully you guys can get more from this than I.

Expected Behavior

Changes to data and rerendering should not orphan elements and fail to delete them.

Current Behavior

Changes to data and rerendering sometimes (seemingly randomly) fails to delete elements.
Throws either TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'. or TypeError: Cannot read property 'parentNode' of null

Steps to Reproduce (for bugs)

https://stackblitz.com/edit/js-mennea

The project has a working example. To see behavior, open the console and click the 'Regenerate x1000' button. Errors will randomly happen.

Context

As the example alludes to I have a tree of folder-like things that I want to display in a tree structure. Based on navigation, I want to be able to show a parent folder with child subfolders, then open one of the subfolders and view it as root. (Generic file system.)

Your Environment

  • Version used: 1.1.6
  • Browser Name and version: Chrome
  • Operating System and version (desktop or mobile): Desktop

All 7 comments

After playing with this more, it seems like these errors go away if you remove the uncertanty.

The first block causing problems is fixed if you always insert a node in it's spot.

This ...

 if (item.children)
   output.push(
     m('.child', item.children ? generateItems(item.children) : ''),
   )

... becomes ...

output.push(
   m('.child', item.children ? generateItems(item.children) : ''),
)

... or ...

output.push(
  item.children ? m('.child', generateItems(item.children)) : null
)

The second block is fixed if you insert a mithril element instead of an unknown number of items.

This ...

output.push(
  someSizedArray.map((item, index) => m('.row', 'some row'))
)

... becomes ...

output.push(
  m('', someSizedArray.map((item, index) => m('.row', 'some row')))
)

Still not sure what causes this in the first place.

I'm facing the same issue (using v1.1.6) and finally was able to track it down. See fiddle: http://jsfiddle.net/tokafew420/e6w8txLd/4/

To reproduce the issue, just step through the example.

The problem arises in a very specific scenario and chain of events. I just started digging through the code so I'm not too familiar with it but I'll try to explain what I think is happening in my example.

Required state:

  • Must use document fragments.
  • Must get into a state where a pool is created.
  • Must get into a state where recycling is taking place.

Steps in example:

  • Step 1 - Just a starting point.
  • Step 2 - Render different (2nd) fragment with data.
  • Step 3 - Reduce the list (This will create a pool)
  • Step 4 - Render first fragment again. This puts us into a state to make the bug happen.
  • Step 5 - Render the 2nd fragment again with the same length as Step 2.
    At this point, the internal state is corrupted already. You can see that the list is NOT in order.
    Line 557 of mithril.js will take the old.dom (which is now the same reference as vnode.dom because of the recycling), and append it to parent. This puts the element at the bottom of the list.
  • Step 6 - Render another state that will cause the nodes to be deleted and this error will happen:
mithril.js:816 Uncaught TypeError: Cannot read property 'parentNode' of null
    at removeNodeFromDOM (mithril.js:816)
    at continuation (mithril.js:803)
    at removeNode (mithril.js:794)
    at removeNodes (mithril.js:774)
    at updateNodes (mithril.js:607)
    at updateFragment (mithril.js:653)
    at updateNode (mithril.js:628)
    at updateNodes (mithril.js:537)
    at updateElement (mithril.js:687)
    at updateNode (mithril.js:629)

This is because the vnode.dom is assumed to be the first-child and the removal process uses this assumption and traverses the nextSibling property to remove all children of the parent. Unfortunately since the node is moved to the last-child position, the nextSibling property is null. Hence the error.

Now, the question is...how do I fix this?

@MynockSpit thanks for the report, somehow I let this one slip, sorry.

@tokafew420 thanks for the repro and analysis.

This was probably fixed in next (the pool was heavily reworked) before we decided to shelve the pool altogether for v2.

As a workaround, you can opt out of the pool by setting a hook on the fragment such as oncreate or onupdate on the children of the fragment. There's no other way to opt out of it.

This is like #1991 , was fixed in next.

Thanks for the response @pygy
I was able to work-around the issue by wrapping the children array. This work-around also fixed the issue in @MynockSpit 's example. I simply changed line 53 in his example from
return output;
to
return m('', output);

I saw that adding a hook will opt out of the pool but when returning arrays it's difficult to add the hook inline.

Looking forward to next.

@tokafew420 For hooks on fragments, you can use m.fragment({oncreate(){}}, [...children]}). m('', ...) adds a wrapping div.

See here for @MynockSpit's example updated accordingly.

Thanks for the insight. I totally skimmed pass that section. Lol

Was this page helpful?
0 / 5 - 0 ratings

Related issues

isiahmeadows picture isiahmeadows  路  4Comments

pygy picture pygy  路  4Comments

StephanHoyer picture StephanHoyer  路  4Comments

millken picture millken  路  4Comments

dhinesh03 picture dhinesh03  路  4Comments