Reputation: 91655
In my Vue project I'm using vue-router. When I navigate away from a certain page I get a vague error and the router stops working. Clicking links updates the URL in the browser, but the app won't navigate to the respective pages.
The error is
TypeError: Cannot read properties of null (reading 'parentNode')
The page in question uses a watcher to update the route query
parameters when the user changes filtering and sorting options. This way, when the page is refreshed/copied/bookmarked, the same options will be selected on their return.
watch(
() => route.query,
() => {
selectedAddresses.value = selectedAddressesQuery.value
selectedStatuses.value = selectedStatusesQuery.value
selectedSortOrder.value = selectedSortOrderQuery.value
},
{ deep: true }
)
watch(
() => [
selectedAddresses.value,
selectedStatuses.value,
selectedSortOrder.value,
],
async () => {
router.replace({
query: {
...route.query,
address: selectedAddresses.value,
sort: selectedSortORder.value,
status: selectedStatuses.value,
},
})
}
)
Why am I getting this error when I navigate away from the page?
Upvotes: 12
Views: 22260
Reputation: 5465
Had the same question but, in my case I found the cause was <Suspense>
where the slots for #default
or #fallback
presented a source containing more than one root element or just text.
Despite Vue3 allowing multiple root elements, components used for route views should still only have one root element
The following will cause such an error if presented in #default
or #fallback
slots:
/** BAD: A lone string **/
<template #fallback>
Loading...
</template>
/** GOOD: Wrapped in a single DIV **/
<template #fallback>
<div>Loading...</div>
</template>
/** BAD: More than one root element in a routed component **/
<template>
<component :is="Component">
<div>Line 1</div>
<div>Line 2</div>
</component>
</template>
/** GOOD: Single root element **/
<template>
<component :is="Component">
<div>
<div>Line 1</div>
<div>Line 2</div>
</div>
</component>
</template>
Upvotes: 3
Reputation: 7
In my case, the problem was caused by h function that was used in template, like this:
In template:
<e-button type="primary" :icon="h(SearchOutlined)"></e-button>
When I use a variable to store the result of the h function and then use this variable in the template, everything works fine, for example like this:
In template:
<e-button type="primary" :icon="icon_SearchOutlined"></e-button>
In script:
const icon_SearchOutlined = h(SearchOutlined)
Upvotes: 0
Reputation: 526
I ran into this on our project that's using vue-router and transitions (vue@3.4.21 and vue-router@4.3.0). It looks like the issue was introduced because router-view is no longer allowed inside of a transition. However, placing it inside of the transition causes the error. Still trying to figure out what's going on.
Upvotes: 0
Reputation: 653
In my case, the problem was due to a child component rendering because of a value in the store and in the async setup script of that child component, the store gets mutated to a value implying that the child component should not be rendered in the v-if.
aStore.getter
returns true
<template>
<ComponentA v-if="aStore.getter" />
<ComponentB v-else />
</template>
Inside script setup of ComponentA
<script lang="ts" setup>
const aStore = useStore()
await aStore.runAction() // afterwards, aStore.getter returns false
</script>
Upvotes: 0
Reputation: 2301
In my case the problem was with <Teleport>
.
The target ("to") node was not there (because of v-if
) at the time when the page which has <Teleport>
tried to render.
So, you could try to comment out <Teleport>
s and see if it helps
Upvotes: 0
Reputation: 91655
The problem was the watcher was being triggered when navigating away from the page because the query gets cleared, technically changing it.
The solution was to add a guard statement to the watch handler to check if we're still on the current page.
watch(
() => route.query,
() => {
// prevent triggering on page leave, which clears the query
if (route.name !== 'invoices') return
selectedAddresses.value = selectedAddressesQuery.value
selectedStatuses.value = selectedStatusesQuery.value
selectedSortOrder.value = selectedSortOrderQuery.value
},
{ deep: true }
)
This prevents the second watcher (that watches those values being set) from being triggered, which was trying to update the route that is no longer there.
Upvotes: 6