VUE 3 Force unmount when deactivated

Viewed 1813

I have VUE app which uses keep-alive method in the router. Many pages needs to be loaded only once but some specific pages need to be loaded again each time activated.

<template>
  <router-view v-slot="{ Component }">
    <keep-alive max="5">
      <component :is="Component" />
    </keep-alive>
  </router-view>
<template>

What I want to do is to unmount a page-component from keep-alive cache when it is deactivated.

/* this is the loaded component */
export default {
  setup() {
    const state=ref({})
    /*init and load data...*/
   
    onDeactivated(()=>{
     //unmount this component ???
    });
    
    return { state };
  },
  components: {},
};

What is the best practice you think?

2 Answers

Conditional keep-alive

You could provide methods for child routes to add/remove themselves to keep-alive's exclude property via a keepAliveExcludes reactive set. Expose the ref to the template as a computed array (the exclude prop requires Array, not Set):

// App.vue
import { provide, reactive, computed } from 'vue'

export default {
  setup() {
    const keepAliveExcludes = reactive(new Set())

    provide('keepAliveExcludes', {
      add: name => keepAliveExcludes.add(name),
      remove: name => keepAliveExcludes.delete(name),
    })

    return {
      keepAliveExcludes: computed(() => Array.from(keepAliveExcludes))
    }
  }
}

In the app template, bind keepAliveExcludes to <keep-alive>.exclude:

<template>
  <router-view>
    <keep-alive :exclude="keepAliveExcludes">
      ...
    </keep-alive>
  </router-view>
</template>

In the child route, inject the keepAliveExcludes, and call its add/remove with the component's name when needed (e.g., via checkbox):

<template>
  <h1>About</h1>
  <div>
    <label>Keep Alive
      <input type="checkbox" v-model="keepAlive">
    </label>
  </div>
</template>

<script>
import { ref, inject, watchEffect } from 'vue'

export default {
  name: 'About',
  setup() {
    const { add: addKeepAliveExclude, remove: removeKeepAliveExclude } = inject('keepAliveExcludes', {})

    const keepAlive = ref(true)
   
    watchEffect(() => {
      if (keepAlive.value) {
        removeKeepAliveExclude?.('About')
      } else {
        addKeepAliveExclude?.('About')
      }
    })

    return { keepAlive }
  },
}
</script>

demo 1

Always exclude from keep-alive

If you need to always exclude the component from the keep-alive cache, you could simply set the component name statically without the complexity of the provide/inject above.

In the app template, specify the component's name (must match the name prop in the component declaration) in <keep-alive>.exclude:

<template>
  <router-view>
    <keep-alive exclude="About">
      ...
    </keep-alive>
  </router-view>
</template>

demo 2

@tony19 posted an awesome answer but due to a possible bug of VUE (currently I use 3.2.6) it has some issues.

When a component excluded from keep-alive after initiated and then after the max keep-alive components reached, VUE tries to remove the non existent excluded component.

This leads to an error and stalls the app. See below for more about the error.

My solution relies on tony19's mind blowing solution.

Instead of excluding the component after loading it, I added rules to router just before loading the component.

const routes = [
  /* ... */
  {
    path: "/about",
    name: "AboutPage",
    meta: { excludeKeepAlive: true },
    component: () => import('./../components/AboutPage.vue'),
  }
  /* ... */
];

and then in the main APP;

/* This is the main app */
export default {
  setup() {
    /* ... */
    const keepAliveExcludes = reactive(new Set());

    cont keepAliveExcluder= {
      add: name => keepAliveExcludes.add(name),
      remove: name => keepAliveExcludes.delete(name),
    }
   
    router.beforeEach((to, from, next) => {    
      if(to.meta?.excludeKeepAlive===true){      
        keepAliveExcluder.add(to.name);
      } 
      next();
     });
    /* ... */
   return {
      keepAliveExcludes: computed(() => Array.from(keepAliveExcludes))
    }
   }
}

More about the error (possibly a bug)

Error occurs in runtime-core.esm-bundler.js:2057

Uncaught (in promise) TypeError: Cannot read property 'type' of undefined

VUE cannot find dynamically excluded component.

screenshot of error

Related