Soviut
Soviut

Reputation: 91655

Why am I getting a "cannot read properties of null reading ParentNode" error from vue-router?

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

Answers (6)

Marc
Marc

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

Calix
Calix

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

Don
Don

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

Jordan Nelson
Jordan Nelson

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

eXception
eXception

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

Soviut
Soviut

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

Related Questions