Bird
Raised Fist0
Vueframework~20 mins

Compound components pattern in Vue - Practice Problems & Coding Challenges

Choose your learning style10 modes available
Challenge - 5 Problems
🎖️
Compound Components Master
Get all challenges correct to earn this badge!
Test your skills under time pressure!
❓ component_behavior
intermediate
2:00remaining
What will be rendered by this compound component?

Consider a Vue compound component where Modal provides context and ModalHeader and ModalBody consume it. What will be the visible output?

Vue
<template>
  <Modal>
    <ModalHeader>Welcome</ModalHeader>
    <ModalBody>This is the body content.</ModalBody>
  </Modal>
</template>

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

const Modal = {
  setup(_, { slots }) {
    const title = ref('Default Title')
    provide('modalTitle', title)
    return () => (
      <section aria-modal="true" role="dialog">
        {slots.default()}
      </section>
    )
  }
}

const ModalHeader = {
  setup(_, { slots }) {
    const title = inject('modalTitle')
    return () => (
      <header><h1>{slots.default ? slots.default() : title.value}</h1></header>
    )
  }
}

const ModalBody = {
  setup(_, { slots }) {
    return () => <main>{slots.default()}</main>
  }
}
</script>
A<section><main>This is the body content.</main></section>
B<section><header><h1>Default Title</h1></header><main>This is the body content.</main></section>
C<section><header><h1></h1></header><main>This is the body content.</main></section>
D<section><header><h1>Welcome</h1></header><main>This is the body content.</main></section>
Attempts:
2 left
💡 Hint

Look at how ModalHeader uses the slot content versus the injected title.

❓ state_output
intermediate
2:00remaining
What is the value of the shared state after clicking the button?

In this compound component pattern, a shared state is provided by the parent and updated by a child button. What will be the final value of count after clicking the button once?

Vue
<template>
  <Counter>
    <CounterDisplay />
    <CounterButton />
  </Counter>
</template>

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

const Counter = {
  setup(_, { slots }) {
    const count = ref(0)
    provide('count', count)
    provide('increment', () => { count.value++ })
    return () => slots.default()
  }
}

const CounterDisplay = {
  setup() {
    const count = inject('count')
    return () => <p>Count: {count.value}</p>
  }
}

const CounterButton = {
  setup() {
    const increment = inject('increment')
    return () => <button onClick={increment}>Add</button>
  }
}
</script>
ACount: 0
BCount: undefined
CCount: 1
DCount: NaN
Attempts:
2 left
💡 Hint

Think about how the increment function changes the shared count state.

📝 Syntax
advanced
2:00remaining
Which option correctly defines a compound component with slots and provide/inject?

Which code snippet correctly implements a compound component pattern in Vue 3 using provide and inject with slots?

A
const Parent = { setup(_, { slots }) { const data = ref('info'); provide('data', data); return () =&gt; &lt;div&gt;{slots.default()}&lt;/div&gt; } };
const Child = { setup() { const data = inject('data'); return () =&gt; &lt;p&gt;{data.value}&lt;/p&gt; } };
B
const Parent = { setup() { const data = ref('info'); provide('data', data); return () =&gt; &lt;div&gt;&lt;slot /&gt;&lt;/div&gt; } };
const Child = { setup() { const data = inject('data'); return () =&gt; &lt;p&gt;{data}&lt;/p&gt; } };
C
const Parent = { setup(_, { slots }) { const data = ref('info'); provide('data', data); return () =&gt; &lt;div&gt;{slots.default}&lt;/div&gt; } };
const Child = { setup() { const data = inject('data'); return () =&gt; &lt;p&gt;{data.value}&lt;/p&gt; } };
D
const Parent = { setup(_, { slots }) { const data = ref('info'); provide('data', data); return () =&gt; &lt;div&gt;{slots.default()}&lt;/div&gt; } };
const Child = { setup() { const data = inject('data'); return () =&gt; &lt;p&gt;{data}&lt;/p&gt; } };
Attempts:
2 left
💡 Hint

Remember that slots.default is a function and inject returns a ref if provided a ref.

🔧 Debug
advanced
2:00remaining
Why does this compound component fail to update the shared state?

Given this compound component, why does clicking the button not update the displayed count?

Vue
<template>
  <Counter>
    <CounterDisplay />
    <CounterButton />
  </Counter>
</template>

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

const Counter = {
  setup(_, { slots }) {
    const count = ref(0)
    provide('count', count)
    provide('increment', () => { count.value++ })
    return () => slots.default()
  }
}

const CounterDisplay = {
  setup() {
    const count = inject('count')
    return () => <p>Count: {count.value}</p>
  }
}

const CounterButton = {
  setup() {
    const increment = inject('increment')
    return () => <button onClick={increment}>Add</button>
  }
}
</script>
ABecause <code>CounterDisplay</code> renders <code>{count}</code> instead of <code>{count.value}</code>, so it shows the ref object, not the number.
BBecause <code>increment</code> is not called on button click due to missing parentheses.
CBecause <code>count</code> is not provided properly in <code>Counter</code> component.
DBecause <code>slots.default()</code> is not called in <code>Counter</code> component.
Attempts:
2 left
💡 Hint

Check how the reactive ref is accessed in the display component.

🧠 Conceptual
expert
2:00remaining
What is the main advantage of using the compound components pattern in Vue?

Why would a developer choose to use the compound components pattern instead of passing props directly to child components?

AIt eliminates the need for slots, simplifying component templates.
BIt allows sharing state and behavior implicitly via provide/inject, enabling flexible composition without prop drilling.
CIt forces all child components to receive props explicitly, improving type safety and clarity.
DIt automatically optimizes rendering performance by memoizing all child components.
Attempts:
2 left
💡 Hint

Think about how state and functions are shared between parent and children without passing props down many levels.

Practice

(1/5)
1. What is the main purpose of the compound components pattern in Vue?
easy
A. To group related UI parts inside one main component for better organization
B. To create multiple unrelated components in one file
C. To avoid using provide and inject for state sharing
D. To write components without any communication between them

Solution

  1. Step 1: Understand the pattern goal

    The compound components pattern groups related UI parts to work together inside one main component.
  2. Step 2: Compare options

    Only To group related UI parts inside one main component for better organization correctly describes grouping related UI parts for better organization and communication.
  3. Final Answer:

    To group related UI parts inside one main component for better organization -> Option A
  4. Quick Check:

    Compound components group related parts = B [OK]
Hint: Think: grouping related UI parts inside one component [OK]
Common Mistakes:
  • Confusing unrelated components grouping
  • Ignoring state sharing between parts
  • Thinking it avoids provide/inject
2. Which Vue feature is commonly used to share state between compound components?
easy
A. provide and inject
B. props
C. v-model
D. emit events

Solution

  1. Step 1: Identify state sharing methods

    Vue offers several ways to share data, but for compound components, provide and inject are designed for parent-child communication without prop drilling.
  2. Step 2: Match pattern usage

    Compound components use provide/inject to share state internally, unlike props or emit which are for direct parent-child or event communication.
  3. Final Answer:

    provide and inject -> Option A
  4. Quick Check:

    State sharing in compound components = provide/inject [OK]
Hint: Remember: provide/inject share state inside compound components [OK]
Common Mistakes:
  • Choosing props which require manual passing
  • Confusing emit with state sharing
  • Thinking v-model is for internal state sharing
3. Given this Vue compound component setup, what will be the rendered output?
const TabGroup = {
  setup() {
    provide('activeTab', ref('home'))
  },
  template: `
` } const Tab = { inject: ['activeTab'], props: ['name'], template: `{{ name }}` } // Usage: // // // //
medium
A. homeprofile
B. homeprofile
C. homeprofile
D. Error: activeTab is not reactive

Solution

  1. Step 1: Understand provide/inject in Options API

    TabGroup provides 'activeTab' as ref('home'). Tab injects ['activeTab'], receiving the ref object (not auto-unwrapped in Options API).
  2. Step 2: Evaluate rendering logic

    In template, activeTab (ref object) === name (string) is always false. No 'active' class applied to either button.
  3. Final Answer:

    <button>home</button><button>profile</button> -> Option B
  4. Quick Check:

    Options API inject ref === string = false [OK]
Hint: Options API inject returns ref object, not value [OK]
Common Mistakes:
  • Assuming inject returns raw value, not ref
  • Confusing which tab is active
  • Thinking activeTab is not reactive
4. What is the main issue in this compound component code?
const Dropdown = {
  setup() {
    const open = ref(false)
    provide('open', open)
  },
  template: `
` } const DropdownToggle = { inject: ['open'], template: `Toggle` }
medium
A. The provide key 'open' is missing
B. The template syntax is invalid
C. The 'open' variable is not returned from setup in Dropdown
D. The 'open' ref is not reactive inside DropdownToggle

Solution

  1. Step 1: Check inject handling in Options API

    Injected refs via inject: ['open'] are not auto-unwrapped in Options API templates. 'open' is the ref object.
  2. Step 2: Understand mutation and reactivity issue

    @click="open = !open" reassigns a boolean to the ref variable (breaks link to original ref) and lacks .value access, making the ref unusable reactively as boolean state.
  3. Final Answer:

    The 'open' ref is not reactive inside DropdownToggle -> Option D
  4. Quick Check:

    Options API injected ref needs .value [OK]
Hint: Use .value for injected refs in Options API templates [OK]
Common Mistakes:
  • Thinking provider setup needs to return state for provide
  • Template syntax looks invalid
  • Provide key seems missing
5. You want to build a compound Tabs component where clicking a tab updates the active tab and content changes accordingly. Which approach correctly implements this pattern in Vue 3?
hard
A. Use local state in each Tab component without sharing activeTab
B. Pass activeTab as prop to each Tab and emit events to Tabs to update state
C. Use provide to share activeTab ref in Tabs, inject in TabList and TabPanels, update activeTab on Tab click
D. Use global event bus to communicate active tab changes between components

Solution

  1. Step 1: Identify compound components pattern usage

    The pattern uses provide/inject to share state like activeTab across nested components without prop drilling.
  2. Step 2: Evaluate options for state management

    Use provide to share activeTab ref in Tabs, inject in TabList and TabPanels, update activeTab on Tab click correctly uses provide to share activeTab ref and inject in child components, updating activeTab on tab click to reflect changes.
  3. Final Answer:

    Use provide to share activeTab ref in Tabs, inject in TabList and TabPanels, update activeTab on Tab click -> Option C
  4. Quick Check:

    Compound components share state via provide/inject = D [OK]
Hint: Use provide/inject to share and update active tab state [OK]
Common Mistakes:
  • Using props and events causing complex prop drilling
  • Using global event bus which is not recommended
  • Keeping state local in each tab causing no sync
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard