svelte icon indicating copy to clipboard operation
svelte copied to clipboard

Uncaught (in promise) TypeError: Cannot read property 'removeChild' of null

Open tanhauhau opened this issue 3 years ago • 34 comments

@jycouet @sudomaxime @torgebauer @orgertot @shedali @clineamb @kilianso if you could kindly post the following about your issues, so that we could better look into it:

  • Reproduction of the issue, github link / svelte repl (https://svelte.dev/repl) (Preferrable)
  • Svelte version you are using (Run yarn why svelte or npm list svelte)

tanhauhau avatar Mar 01 '21 14:03 tanhauhau

My issue is not reproducible with svelte 3.17.3

shedali avatar Mar 01 '21 16:03 shedali

My issue is not reproducible with svelte 3.17.3

So the latest svelte version is 3.34.0, is it fixed since 3.17.3? Or its a regression bug on 3.34.0?

tanhauhau avatar Mar 01 '21 16:03 tanhauhau

I have this behavior with "svelte": "3.34.0" (it was also the case with "3.32.3") (Actually, my exact message is Uncaught TypeError: Cannot read property 'removeChild' of null, there is no (in promise))

Unfortunately, I can't share my repo, and I don't find the root cause to isolate the issue and create a repl. Fortunately, I ❤️ Svelte and will spend time to find how to reproduce it. I hope to be able to create a repl soon

jycouet avatar Mar 01 '21 19:03 jycouet

Sapper-based project, this in a .svelte file crashes the app and requires a reload on navigating away from the relevant Sapper route:

<div class="note-box" bind:clientWidth={elemWidth}>
  {@html svg}
</div>

The element whose parentNode no longer exists is an IFRAME and I assume it has been added to the DIV to help measure width and/or detect resizes. It seems that the logic cleaning up @html blocks on unmount does not take into account the presence of this IFRAME, and the later logic that attempts to clean up the IFRAME does not consider that it might no longer be in the DOM?

I attempted a demo here but I don't know how to make it un-mount the component like it does during navigation - or perhaps there's some other trigger I have not discovered. I hope this helps anyway. https://svelte.dev/repl/e67564eaa8d24312bbeb91e0a402f7fc?version=3.34.0 Edit: using [email protected] for project now, still seeing the problem.

hallvors avatar Mar 01 '21 20:03 hallvors

On my side, I'm not on sapper, just Svelte. And I think that it's link with a removal of an unfinished transition. But after a few hours on it I have to say that I couldn't reproduce it in my REPL 😞

I don't even know where to look at to help ><

jycouet avatar Mar 01 '21 21:03 jycouet

Do you know what element it crashes when it tries to remove? In devtools, you can set a conditional breakpoint on the line that throws and make the condition !element.parentNode - then it will stop just before throwing.

hallvors avatar Mar 02 '21 08:03 hallvors

Thx a lot @hallvors , I never use this debugging way! And it worked very well in my repo.

It's in a component where I have:

<script lang="ts">
    ....
</script>

{#if showEdit}
    ...
{/if}

=> no parentNode.

I updated this component like this:

<script lang="ts">
    ....
</script>

<div>
  {#if showEdit}
      ...
  {/if}
</div>

And now, I don't have the error anymore.

I tried to reproduce here https://svelte.dev/repl/747b8a3626624afab7f1640635190591?version=3.34.0 But I don't manage to have my not working behavior. 😞

Not sure what to do now. Should we close the issue? Or?

jycouet avatar Mar 02 '21 23:03 jycouet

I think the Svelte developers likely want to fix something here, so let's leave that decision to them :)

hallvors avatar Mar 03 '21 08:03 hallvors

I updated this component like this:

And now, I don't have the error anymore.

If it can help, you should take into account the parent creating this component in order to reproduce this issue. IIRC there are a lot of "anchoring" cases when your component has only a condition on the top level, and the anchor is provided by the parent.

j3rem1e avatar Mar 03 '21 08:03 j3rem1e

@jycouet

let me check if understand your situation correctly, so it is still throwing errors if you do this in a component?

<script lang="ts">
    ....
</script>

{#if showEdit}
    ...
{/if}

and it occurs when you toggling on/off of the showEdit?

Maybe can explain further on how this component is being used by the parent

  • whether there's other logic that tries to show / hide the component
  • any transitions involved
  • any actions involved manipulating the dom elements

tanhauhau avatar Mar 04 '21 02:03 tanhauhau

@tanhauhau it's already a component.


Let me try to explain with a drawing showing the different steps: Issue 6037 It's what I want and what I did here: https://svelte.dev/repl/747b8a3626624afab7f1640635190591?version=3.34.0 As you can see in the REPL, you have no errors, and it's working well.

On my project, and in browser debug mode (thx @hallvors) I have the steps 3.1 and 4.1 added Issue 6037_2

So I'm closing the first Dialog before the second one. I think that the issue I have right now is logic that tries to show / hide the components in the wrong way. => As it's working well in the REPL, I will refactor my current spaghetti.

Notes:

  • I have 2 transitions in the dialog component
  • I have 0 action involved manipulating the dom elements

In the end, I think that it's me and my own logic and not really an issue for Svelte. Maybe I'm opening a pandora box, but what could be nice is that Svelte tells you: "Nothing to remove on xxx"

jycouet avatar Mar 04 '21 09:03 jycouet

https://github.com/sveltejs/svelte/issues/2086#issuecomment-787896877 @jycouet You asked me how I managed to workaround the issue. You will find the "root cause" in npm_modules/svelte/internal/index.js line 202 (varies with your svelte version) For local development you can simple change it there but this will not help you for production. My simple and really stupid solution is to string replace it during the bundle Changes in my rollup.config.js transform(code, id) { return code.replace('node.parentNode.removeChild(node)', 'if(node.parentNode)node.parentNode.removeChild(node)'); } } I checked the issue also with a conditional breakpoint and a lot of things were related to animations with svelte/transition. I changed all transition to local transitions. But like I said it is still an issue for us and I decided to dirty fix it with the string replacement. When I find some time I can try again to isolate the issue with conditional breakpoints.

We do similar things like you: Open closing dialogs and overlays and changing big parts of the DOM. We have a pretty interactive page (mini games) and we are using a lot of different components which are dynamically shown and hidden all the time.

torgebauer avatar Mar 10 '21 08:03 torgebauer

Just in case it's useful to anyone else, here's what specifically caused this issue for me and how I solved it:

My code:

{#each headers as header}
      <th on:click="{() => sortTable(header.key)}">
          {#if searchParams.sort === header.key && searchParams.order === 'asc'}
             <span><span style="height: 10px;" class="iconify" data-icon="fa:angle-up" data-inline="false"></span></span>
          {:else if searchParams.sort === header.key && searchParams.order === 'desc'}
             <span><span style="height: 10px;" class="iconify" data-icon="fa:angle-down" data-inline="false"></span></span>
          {/if}
          {header.name}
      </th>
{/each}

Basically, I have a sortable table where I wanted to place an icon next to the header of the column being sorted. I'm using Iconify icons and inserting them using <span> tags, but when the icons are actually loaded, the <span>s get converted to <svg>s. So, when I clicked the header to change the sort direction from ascending to descending, the <span> tag was no longer there for Svelte to remove. My solution above to to wrap an additional span around each element, so that that element can be located and targeted for removal.

apop880 avatar Mar 30 '21 14:03 apop880

Hello! I'd like to just bump this issue a bit and perhaps (hopefully) increase the urgency for a solution.

We're seeing a lot of issues with users using Google Translate. Some cases we've solved by simply adding "notranslate" classes to affected components, but we'd really like to avoid spraying that all over our app.

If you want to see it in action, you can reproduce one case like this:

  • Enable translation for german
  • Visit https://contabo.com/de/about-us/
  • Navigate to the "Careers" page using the menu

OskarHeden avatar Apr 09 '21 08:04 OskarHeden

Hello there ! I can confirm one of by beta users stumbled upon this bug while using automatic translations on Google Chrome. When I suggested her to try to turn the translation off, it worked like a charm.

Nokorbis avatar Apr 24 '21 15:04 Nokorbis

You will find the "root cause" in npm_modules/svelte/internal/index.js line 202 (varies with your svelte version) For local development you can simple change it there but this will not help you for production. My simple and really stupid solution is to string replace it during the bundle Changes in my rollup.config.js transform(code, id) { return code.replace('node.parentNode.removeChild(node)', 'if(node.parentNode)node.parentNode.removeChild(node)'); } }

Thanks for this workaround. Instead of configuring rollup to change output bundle, I use https://www.npmjs.com/package/patch-package - this works both for local development and for production bundle

janproch avatar Apr 29 '21 08:04 janproch

Related to @apop880 comment (https://github.com/sveltejs/svelte/issues/6037#issuecomment-810304989), I encountered this issue recently when using fontawesome icons that I did not have wrapped in tags - once I wrapped them in tags, went away. Was not an issue previously but I am unsure how many versions ago that was.

kbitz avatar Jun 04 '21 13:06 kbitz

In my case, this error appears if I am sending HTML to a component slot. So <Copy>{@html item.content.description}</Copy> with Copy.svelte: <p class="text-xl mb-2"> <slot /></p> throws this error, because the node is detached.

Also the HTML in the elements tab looks wrong to me: <div class="h-full block p-4"><div class="text-3xl mb-3"><!-- HTML_TAG_START -->Bla<!-- HTML_TAG_END --></div> <p class="text-xl mb-2"><!-- HTML_TAG_START --></p><h2>dsfdsfdf<b>dfdsf</b></h2><!-- HTML_TAG_END --><p></p></div>

caboe avatar Jul 11 '21 12:07 caboe

I am encountering the same issue. I am working with svelte-tiny-virtual-list, building an image viewer that allows zooming (which results in different images being in scope for rendering depending on the zoom level). When zooming in and out too quickly, that error is thrown.

inzanez avatar Jul 21 '21 16:07 inzanez

I have a repro of this issue involving Twitter embeds that are rendered within a modal where embed code is rendered using @html — the issue reproduces when closing the modal if the content rendered by @html is not wrapped in a tag:

https://github.com/pushred/svelte-issue-6037-repro/blob/main/src/routes/index.svelte

(freshly initialized SvelteKit project, only changes are in the above file)

I suspect this is due to the Twitter's widgets.js swapping out the original markup with it's own. As someone mentioned in #2086 this seems to get Svelte in a state where it is "confused" and tries to cleanup the prior DOM state.

My repro also has an option that demonstrates the workaround of wrapping @html in a tag.

pushred avatar Aug 15 '21 16:08 pushred

I get this issue in sentry from users sometimes. In my case I get it in different components (can't see any consistancy) Currently events were received only from chrome (fresh versions tho). Svelte version: 3.42.1 image

I also can see similar errors in my console sometimes during HMR (but it's pretty laggy in my case because i am injecting svelte into vue) Hope my info is useful a bit

ZerdoX-x avatar Sep 17 '21 11:09 ZerdoX-x

This seems to be a regression bug. Fixed here: https://github.com/sveltejs/svelte/issues/2086#issuecomment-490989491 Broken again here: https://github.com/sveltejs/svelte/commit/6d16e9260642b1fcc70fa4a24be9fd49985112d1 Seems like this got caught in a revert even though it wasn't related.

export function detach(node: Node) {
	if (is_hydrating) {
		nodes_to_detach.add(node);
	} else if (node.parentNode) {
		node.parentNode.removeChild(node);
	}
}

I'm using maplibre-gl and trying to make markers utilize slots for their HTMLElement property, but when cleaning up the marker it removes the element before Svelte does and Svelte gets confused.

Best solution would be just to return the original fix.

export function detach(node: Node) {
	if (node.parentNode) {
		node.parentNode.removeChild(node);
	}
}

byt3rr avatar Sep 27 '21 17:09 byt3rr

https://svelte.dev/repl/0d826c0d67c54d8693aeaa1b77ea09ef?version=3.44.2

Very simple REPL to reproduce this error.

Just click the buttons in order 👍

Not sure how the suggested/previous solution should've caused side effects though 🤔

Here is a test for svelte that would fail due to this => https://github.com/TorstenDittmann/svelte/tree/fix-destroy-missing-fragment/test/runtime/samples/destroy-missing-fragment

TorstenDittmann avatar Dec 07 '21 00:12 TorstenDittmann

#2086 (comment)

I encountered this bug in a similar context — trying to toggle/destroy a custom Marker component on a mapbox map, built with beyonk/svelte-mapbox library. Likewise, the fix that @byt3rr specified from @ciri worked for me.

Change line 306 (this line number changes with updates) of node_modules/svelte/internal/index.mjs to:

function detach(node) {
	if(node.parentNode) {
		node.parentNode.removeChild(node);
	}
}

It appears the line number to make this change (currently 306) periodically changes with new updates. For future reference, to find where to insert this snippet, just look in node_modules/svelte/internal/index.mjs for the function:

function detach(node) {
    node.parentNode.removeChild(node);
}

And replace that function with:

function detach(node) {
	if(node.parentNode) {
		node.parentNode.removeChild(node);
	}
}

sbutler-gh avatar Dec 22 '21 13:12 sbutler-gh

Thank you @sbutler-gh , this just saved me big time.

stolinski avatar Jan 06 '22 23:01 stolinski

This problem is still happening witn the latest svelte. I'm not sure if there was a regression here:

"svelte": "^3.46.6",

function detach(node) {
    node.parentNode.removeChild(node);
}

metayii avatar Apr 01 '22 21:04 metayii

We started getting this error a couple of days ago:

"svelte": "^3.46.4"

Screen Shot 2022-04-11 at 12 16 50

pedroborges avatar Apr 11 '22 15:04 pedroborges

Please don't make "the bug is still happening" comments. We already know that because the issue is still open.

ceifa avatar Apr 14 '22 20:04 ceifa

Since this is a long running issue, with multiple issues opened for 3 years, I thought that maybe I did something wrong in my Svelte code. And I don't really want to patch the Svelte code. I tried to add multiple span or div to wrap various items like icons, nothing works. And I finally identified the root cause was a DOM.remove() I'm using to manage a list and front-run the Svelte mechanism to dismiss an element. Because on a table it is easier to control like that the contents and controlling out the animation.

It was like that :

{#each el as elts, i}
    <div id="domRemoved"}
         ...
    </div>
{/each}

And the fix is :

{#each el as elts, i}
    <div>
        <div id="domRemoved"}
            ...
        </div>
    </div>
{/each}

To me, this issue is not an issue in Svelte, but an issue on how developers are using it.

How does it happen ?

When you have a DOM which is just inside an Svelte block, like {#if ..} or {#each ...}, and a JS script is removing this DOM. The removal is done by JavaScript outside of the knowledge of Svelte. At the end, when a page change (e.g. SPA routing), when Svelte kills all the DOM of the page, there's no element remaining in that Svelte block, and the crash occurs.

It can be for example :

  • Your JS calls dom.remove() for a DOM just inside a Svelte block
  • An icon script, changing a span DOM directly inside a Svelte block into a SVG (this removes the span DOM)
  • You have {value} which can be empty directly inside a Svelte block (to be confirmed)

How to fix it?

Basically, make sure there isn't any script removing a component or DOM directly inside a Svelte block. A way is to wrap the DOM in a DIV or SPAN new block. So that when removing it, there's still a DOM inside the Svelte block. An other way is to prevent using any JS that removes a DOM. Not easy way because there are some scripts required sometimes, we can't escape this. This way can mean "use more Svelte" to achieve your goal.

In a way, the core issue is that Svelte is unsynced with the DOM, and is not aware of a DOM removal, than when it closes it, the element was already removed and doesn't exist anymore. So wrap it as a child, or refrain to use any DOM removal from JS. My PoV is that this is not really an issue in Svelte.

Still, adding if(node.parentNode) can help, because if there's nothing to delete, it won't crash anymore. For now, just make sure there's always something to delete.

Also, this can make it clear as a warning in the documentation. I haven't seek about this in the doc so far.

antonio-fr avatar Apr 29 '22 16:04 antonio-fr

Thank you @antonio-fr for the guidance. You put me on the right track after many hours of aimless debugging.

If it helps anyone else, my situation was a top-level <Nav /> element shown conditionally:

{#if $state.navigation}
    <nav transition:fade|local>
    ...
    </nav>
{/if}

Wrapping the entire block in a div resolved it for me: <div>{#if}...{/if}</div>

brodysmith1 avatar Jun 21 '22 14:06 brodysmith1