Skip to the solution
MediumFrontend10 min read

How do dynamic components work?

75 views

The problem

Question presented to candidate: "Our settings page has five tabs and the tab bar is driven by config from the server. Instead of a long v-if / v-else-if chain, a colleague used <component :is>. Explain how that works, what you can pass to :is, and what happens to a tab's state when the user switches away."

What a strong answer should cover:

  • <component :is="x"> is a built-in placeholder; the compiler turns it into resolveDynamicComponent(x) and renders whatever that returns.
  • :is accepts a component object (the usual case in <script setup>), a registered component name string, or a plain HTML tag name.
  • Holding a component object in state should use shallowRef or markRaw; ref() makes it deeply reactive and Vue warns about it.
  • Switching :is unmounts the old component and mounts the new one, so local state is lost unless the switch is wrapped in <KeepAlive>.
  • Props, attrs, events and slots passed to <component> go to whichever component is current, so tabs need a compatible interface.
  • An unknown string name silently renders as a custom element, so typos do not throw.

Clarifying questions expected:

  • "Should a tab keep its half-filled form when the user switches away and back?"
  • "Is the list of tabs fixed at build time, or does it come from data at runtime?"

Code / implementation expected: Yes β€” a small tab switcher with <component :is> and a note on what switching does to state.

  • dynamic-components

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

10 min read Β· 10 sections

Read the code

VueTab switcher with <component :is>, a shallowRef and a dynamic wrapper tag
<script setup>
import { ref, shallowRef, defineComponent, h, onMounted, onUnmounted } from "vue";

// Three tab bodies, defined inline so this file is self-contained.
const Profile = defineComponent({
  name: "Profile",
  setup() {
    const draft = ref("");
    onMounted(() => console.log("[Profile] mounted"));
    onUnmounted(() => console.log("[Profile] unmounted"));
    return () =>
      h("label", ["Bio: ", h("input", { value: draft.value, onInput: (e) => (draft.value = e.target.value) })]);
  },
});
const Billing = defineComponent({
  name: "Billing",
  props: { plan: String },
  setup(props) {
    onMounted(() => console.log("[Billing] mounted"));
    onUnmounted(() => console.log("[Billing] unmounted"));
    return () => h("p", `Plan: ${props.plan}`);
  },
});

const tabs = { Profile, Billing };
// shallowRef: hold the component object itself, do not make it deeply reactive.
const current = shallowRef(Profile);
// :is also accepts a plain HTML tag name.
const wrapper = ref("section");
</script>

<template>
  <nav>
    <button v-for="(comp, name) in tabs" :key="name" @click="current = comp">{{ name }}</button>
    <button @click="wrapper = wrapper === 'section' ? 'article' : 'section'">Toggle wrapper</button>
  </nav>
  <component :is="wrapper" class="panel">
    <component :is="current" plan="Pro" />
  </component>
</template>

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 71 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextHard

What are v-once and v-memo?

Continue