Fix: KeepAlive Should Contain Exactly One Component Child

Error message:
KeepAlive should contain exactly one component child.
Components 2025-01-25

What Causes This Warning?

This warning occurs when <KeepAlive> contains multiple children or no children. The <KeepAlive> component is designed to cache a single dynamic component at a time.

The Problem

<!-- ❌ Multiple children -->
<template>
  <KeepAlive>
    <ComponentA />
    <ComponentB />
  </KeepAlive>
</template>

<!-- ❌ No children -->
<template>
  <KeepAlive>
  </KeepAlive>
</template>

<!-- ❌ Text or elements instead of component -->
<template>
  <KeepAlive>
    <div>Not a component</div>
  </KeepAlive>
</template>

The Fix

Use with Dynamic Component

<script setup>
import { shallowRef } from 'vue'
import TabA from './TabA.vue'
import TabB from './TabB.vue'

const currentTab = shallowRef(TabA)
</script>

<template>
  <!-- ✅ Single dynamic component -->
  <KeepAlive>
    <component :is="currentTab" />
  </KeepAlive>

  <button @click="currentTab = TabA">Tab A</button>
  <button @click="currentTab = TabB">Tab B</button>
</template>

Use with v-if

<script setup>
import { ref } from 'vue'
const activeTab = ref('home')
</script>

<template>
  <!-- ✅ Only one component rendered at a time -->
  <KeepAlive>
    <HomeTab v-if="activeTab === 'home'" />
    <SettingsTab v-else-if="activeTab === 'settings'" />
    <ProfileTab v-else-if="activeTab === 'profile'" />
  </KeepAlive>
</template>

Use with Vue Router

<template>
  <!-- ✅ Keep route components alive -->
  <RouterView v-slot="{ Component }">
    <KeepAlive>
      <component :is="Component" />
    </KeepAlive>
  </RouterView>
</template>

Common Scenarios

Tab Navigation

<script setup>
import { ref, markRaw } from 'vue'
import Overview from './Overview.vue'
import Details from './Details.vue'
import Settings from './Settings.vue'

const tabs = [
  { name: 'Overview', component: markRaw(Overview) },
  { name: 'Details', component: markRaw(Details) },
  { name: 'Settings', component: markRaw(Settings) },
]

const activeTab = ref(tabs[0])
</script>

<template>
  <div class="tabs">
    <button
      v-for="tab in tabs"
      :key="tab.name"
      :class="{ active: activeTab === tab }"
      @click="activeTab = tab"
    >
      {{ tab.name }}
    </button>
  </div>

  <!-- ✅ Single cached component -->
  <KeepAlive>
    <component :is="activeTab.component" />
  </KeepAlive>
</template>

Include/Exclude Components

<template>
  <!-- Only cache specific components -->
  <KeepAlive include="ComponentA,ComponentB">
    <component :is="currentComponent" />
  </KeepAlive>

  <!-- Exclude specific components from cache -->
  <KeepAlive exclude="HeavyComponent">
    <component :is="currentComponent" />
  </KeepAlive>

  <!-- Use regex -->
  <KeepAlive :include="/Tab.*/">
    <component :is="currentComponent" />
  </KeepAlive>
</template>

Limit Cache Size

<template>
  <!-- Only keep last 5 components in cache -->
  <KeepAlive :max="5">
    <component :is="currentComponent" />
  </KeepAlive>
</template>

With Conditional Rendering

<!-- ❌ Both might render -->
<KeepAlive>
  <ComponentA v-show="showA" />
  <ComponentB v-show="!showA" />
</KeepAlive>

<!-- ✅ Only one renders -->
<KeepAlive>
  <ComponentA v-if="showA" />
  <ComponentB v-else />
</KeepAlive>

Lifecycle Hooks with KeepAlive

<script setup>
import { onActivated, onDeactivated } from 'vue'

// Called when component is activated from cache
onActivated(() => {
  console.log('Component activated')
  // Refresh data, resume animations, etc.
})

// Called when component is deactivated (cached)
onDeactivated(() => {
  console.log('Component deactivated')
  // Pause animations, clear timers, etc.
})
</script>

Why Only One Child?

<KeepAlive> works by:

  1. Caching the vnode of its child component
  2. Restoring it when the child is rendered again
  3. Managing a single active component at a time

Multiple children would make the caching logic ambiguous.

Quick Checklist

  • Use <component :is> for dynamic components
  • Use v-if/v-else for conditional components (not v-show)
  • Ensure only one child component at a time
  • Use include/exclude for selective caching
  • Set max to limit memory usage
  • Use onActivated/onDeactivated for lifecycle hooks