vue-fragment
vue-fragment copied to clipboard
"TypeError: Cannot read property 'insertBefore' of null"
I'm still getting the `` error with the latest version of this. I did notice most of the other instances of this error have gone away.
index.js?1ef8:2178 [Vue warn]: Error in directive fragment inserted hook: "TypeError: Cannot read property 'insertBefore' of null"
found in
---> <Fragment>
<HelpLink> at components/helpers/help-link.vue
<DeviceList> at components/device-list/index.vue
<Home> at components/home/index.vue
<DetectNetwork> at components/helpers/detect-network.vue
<App> at components/app.vue
<Root>
index.js?1ef8:2178 TypeError: Cannot read property 'insertBefore' of null
at inserted (vue-fragment.esm.js?3f08:1)
at callHook$1 (vue.runtime.esm.js?2b0e:6289)
at callInsert (vue.runtime.esm.js?2b0e:6228)
at wrappedHook (vue.runtime.esm.js?2b0e:2076)
at Object.invoker [as insert] (vue.runtime.esm.js?2b0e:2019)
at invokeInsertHook (vue.runtime.esm.js?2b0e:5956)
at VueComponent.patch [as __patch__] (vue.runtime.esm.js?2b0e:6175)
at VueComponent.Vue._update (vue.runtime.esm.js?2b0e:2666)
at VueComponent.updateComponent (vue.runtime.esm.js?2b0e:2784)
at Watcher.get (vue.runtime.esm.js?2b0e:3138)
at Watcher.run (vue.runtime.esm.js?2b0e:3215)
at flushSchedulerQueue (vue.runtime.esm.js?2b0e:2977)
at Array.eval (vue.runtime.esm.js?2b0e:1833)
at flushCallbacks (vue.runtime.esm.js?2b0e:1754)
This is my component with the issue. I've commented out the section where the error is coming from. If I use the v-else
that's not commented out I get no errors and everything works fine. There's a very good chance I'm just using this incorrectly.
<template>
<component v-if="tag && !tagExcluded" :is="tag">
<component :is="component">
<slot></slot>
</component>
</component>
<component v-else-if="tag && tagExcluded" :is="component">
<slot></slot>
</component>
<div v-else style="color:red;">{{ component }}</div>
<!-- <component v-else :is="component">
<slot></slot>
</component> -->
</template>
<script>
/**
* @name HelpLink
* @description Open help box on click
* @prop {string} [name='*']
* @prop {boolean} [exclude=true]
* @prop {boolean} [forceExclude=false]
* @prop {string} [tag]
*/
export default {
name: 'help-link',
data() {
return {
show: false,
el: null
};
},
props: {
name: {
type: String,
default: '*'
},
exclude: {
type: Boolean,
default: true
},
forceExclude: {
type: Boolean,
default: false
},
tag: {
type: String
}
},
methods: {
handler() {
this.show = !this.show;
this.$bus.emit(`help:${this.name}:${this.show ? 'show' : 'hide'}`);
}
},
computed: {
tagExcluded() {
const { exclude } = this
const excluded = [
'table',
'thead',
'tbody',
'td',
'tr',
'button'
];
return this.forceExclude || (this.exclude && excluded.includes(this.$slots.default[0].tag));
},
component() {
if (this.tagExcluded) {
return 'fragment';
}
return 'a';
}
},
created() {
// Set key to name
this.$vnode.key = this.name;
},
mounted() {
// Set cursor
this.$el.style.cursor = 'help';
// Add emitter to click event
this.$el.addEventListener('click', this.handler);
},
beforeDestroy() {
this.$el.removeEventListener('click', this.handler);
}
}
</script>
I am pretty sure this is the example that's causing the issue with the commented out v-else
.
<thead>
<help-link :name="`${type}Help`">
<td>Device</td>
<td>Identification</td>
<td>Temp.</td>
<td>Reads</td>
<td>Writes</td>
<td>Errors</td>
<td>FS</td>
<td>Size</td>
<td>Used</td>
<td>Free</td>
<td>View</td>
</help-link>
</thead>
There's a very good chance I'm just using this incorrectly.
If so, then maybe i can improve documentation.
A little review of your component to begin with :
-
you don't need
mounted()
andbeforeDestroy()
: you can use@click
and<style>
with<component />
. see http://jsfiddle.net/rL10ewnf/1/ for a working example. -
a better design for your
handler()
is to make the eventbus dispatch when reacting toshow
's new value :watch: { show(newVal) { this.$bus.emit(`help:${this.name}:${newVal ? 'show' : 'hide'}`); } } // ... methods { handler() { this.show = !this.show } }
The benefit is that, if someone makes writes
<help-link ref="helpLink">
, then they can dothis.$refs.helpLink.show = true
orthis.$refs.helpLink.handler()
and your component will not misbehave. Also, because thehandler()
becomes a one-liner, you could do@click="show = !show"
in your template instead and save some lines. -
You're twitching
key
andname
attributes for no reason : you definename
, but use it to set thekey
; why not usingkey
directly ? You can remove@prop {string} [name='*']
and then usethis.$vnode.key
instead. Of course, it means usingkey
instead ofname
while using your component. -
few minor improvement in the syntax of the computed to make them look smaller such as ternary statements and separate enum definition.
-
your template definition is highly complicated for no reason. it took me... 5 real minutes to understand it, which is more than the average of components i see on a daily basis. You can factorize the 2 latest cases (
v-else-if
andv-else
), but even still, I don't know for sure what's the output for each case, and why would you do such a thing.
At the very least, your component should look more like :
<template>
<component v-if="tag && !tagExcluded" :is="tag" class="helplink" @click="show = !show">
<component :is="component"><slot /></component>
</component>
<component v-else :is="component" class="helplink" @click="show = !show">
<slot />
</component>
</template>
<script>
/**
* @name HelpLink
* @description Open help box on click
* @prop {boolean} [exclude=true]
* @prop {boolean} [forceExclude=false]
* @prop {string} [tag]
*/
export const EXCLUDED_TAGS = ['table', 'thead', 'tbody', 'td', 'tr', 'button'];
export default {
name: 'help-link',
props: {
exclude: {
type: Boolean,
default: true
},
forceExclude: {
type: Boolean,
default: false
},
tag: String,
data: () => ({ show: false })
computed: {
tagExcluded() {
return this.forceExclude || (this.exclude && EXCLUDED_TAGS.includes(this.$slots.default[0].tag));
},
component() { return this.tagExcluded ? 'fragment' : 'a' }
},
watch: {
show(newVal) {
this.$bus.emit(`help:${this.$vnode.key}:${this.show ? 'show' : 'hide'}`);
}
},
}
</script>
<style>
.helplink {
cursor: help;
}
</style>
<thead>
<help-link :key="`${type}Help`">
<td>Device</td>
<td>Identification</td>
<td>Temp.</td>
<td>Reads</td>
<td>Writes</td>
<td>Errors</td>
<td>FS</td>
<td>Size</td>
<td>Used</td>
<td>Free</td>
<td>View</td>
</help-link>
</thead>
I honestly don't know what you try to accomplish with that. The way you write is testing for this.$slots.default[0].tag
but you give an example with multiple children, meaning that this.$slots.default[n].tag
will be ignored.
My assumption is that you want to add a click handler and a help cursor on the fly to a bunch of existing elements, and for such, you should probably use a render function or a directive instead.
I get this error when running jest tests on my Vue components. It doesn't cause the test to fail, but lights up my terminal output. No errors show up when running in a browser, however.
Its a pretty standard Nuxt app, I haven't done much to change the standard jest setup so should be easily replicatable. Here's what it looks like when I run jest:
I can see its triggering from my OverviewPage
component. The template looks like this:
<template>
<Fragment>
<Header
:title="pageData.title"
:subtitle="pageData.sub_title"
/>
<StreamField :blocks="pageData.body" />
</Fragment>
</template>
And noting that my StreamField
component there also has a Fragment
at the template root, and is reporting the same kind of errors.
My working knowledge of jest is not great so feeling a little over my head here, any help appreciated 🙏
Additionally, thought I might mention that it appears that all of #42, #26, #24, and #18 are maybe all this same issue? Should they be merged into one spot? The error message is almost the same in each case:
TypeError: Cannot read property 'insertBefore' of null
or
TypeError: Cannot read property 'insertBefore' of Node
I have the same problem as @tbredin . Does anyone know how to get around this situation?
I found a functional approach. Fragment needs a parent to be rendered, and that's exactly what I do.
My component template:
I was having the same issue as @tbredin and @hugo-cardoso's solution worked wonders
I have same: [Vue warn]: Error in mounted hook: "TypeError: Cannot read property 'insertBefore' of null
please help me ...
@roborock upgrade to Vue 3, there's native support of fragments 🙏
I personally solve this by using :tabindex in v-for so vue component know where insert it.
I couldn't get @hugo-cardoso solution to work with a component that uses props. So I used the attachTo
option instead:
const div = document.createElement('div')
div.id = 'root'
document.body.appendChild(div)
wrapper = mount(MyComponent,
{
attachTo: '#root',
propsData: {
propKey: propValue
}
// other options
}
)
Expanding on @SebastianMelinat's solution, one may also easily do
attachTo: document.body
as per the vue-test-utils docs and this comment in the vue-test-utils repo.
For me, this solved the problem completely using Vue2 and Nuxt2 and didn't require me to add any extra code.
The attachTo
trick (either variety) does not work well. It gets rids of the error, but it also makes sure the component is completely empty. When I log html()
on the wrapper, it returns <div fragment="60bcdacce0"></div>
and literally nothing more. The entire contents of the components are gone.
Since the error doesn't really affect the tests succeeding/failing, I thought about eating the error using a try..catch statement. But nope, even that doesn't work, because vue-fragment (or one of its dependencies) is doing a literal console.error()
, which is eludes to bad design.
In component.js line 73 it blindly assumes parent
to be defined, which is not good. Why this produces a console.error
instead of a normal error to be thrown, is beyond me.