Hone logo
Hone
Problems

Dynamic Content Injection with Functional Slots in Vue.js

Vue.js slots are a powerful mechanism for component composition, allowing parent components to inject content into child components. This challenge focuses on a more advanced use case: using functional slots to dynamically render content based on data or logic provided by the parent. This is particularly useful when you need to control the presentation of injected content in a granular way.

Problem Description

Your task is to create a Card component in Vue.js using TypeScript that accepts a functional slot. This functional slot will be provided by the parent component and will receive specific data to render dynamic content within the Card.

Requirements:

  1. Card Component:

    • The Card component should accept a single named slot, let's call it content.
    • This content slot should be a functional slot. This means the parent component will pass a function as the slot's content.
    • The Card component should call this function and render whatever it returns.
    • The Card component should also accept a title prop (string) which will be displayed prominently.
  2. Functional Slot Implementation:

    • The parent component will define a function that takes an object of data as an argument.
    • This function will return a Vue render function or a VNode.
    • The Card component will pass specific data (e.g., item: { id: number; name: string }) to this function.
  3. Parent Component Usage:

    • The parent component will use the Card component.
    • It will provide a title prop to the Card.
    • It will define a function for the content slot that utilizes the data passed from the Card to render specific elements (e.g., a list item with an ID and name).

Expected Behavior:

The Card component should display its title and then render the content generated by the functional slot. The functional slot, when called by the Card component, should use the provided data to dynamically generate and return the desired VNodes.

Edge Cases:

  • What happens if the content slot is not provided? The Card should render without any content below the title.
  • Consider how to handle cases where the data passed to the functional slot might be null or undefined. (For this challenge, assume valid data will be passed.)

Examples

Example 1: Basic Usage

Parent Component (Vue Template - TypeScript):

<template>
  <div>
    <Card title="User Information">
      <template #content="{ item }">
        {{ item.name }} (ID: {{ item.id }})
      </template>
    </Card>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import Card from './Card.vue'; // Assume Card.vue is in the same directory

export default defineComponent({
  components: {
    Card,
  },
  data() {
    return {
      userData: { id: 1, name: 'Alice' },
    };
  },
  // The function passed to the slot will receive `userData`
  // but the Card component is responsible for passing it.
  // For simplicity in the template, we show the result.
  // The actual implementation will involve passing the data to the render function.
});
</script>

Card.vue (Conceptual):

<template>
  <div class="card">
    <h2>{{ title }}</h2>
    <div class="card-content">
      <!-- This is where the functional slot's output will be rendered -->
      <slot name="content" :item="dataToPassToSlot"></slot>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, PropType, h, renderSlot } from 'vue';

interface UserData {
  id: number;
  name: string;
}

export default defineComponent({
  props: {
    title: {
      type: String,
      required: true,
    },
  },
  // This is a simplified view of how the data is passed.
  // The core challenge is understanding how the `slot` API handles functions.
  setup(props, { slots }) {
    const dataToPassToSlot = { id: 1, name: 'Alice' }; // Data from the Card or its own state

    // The actual rendering logic for the slot happens when Vue processes the template.
    // The key is that the `slot` component itself can accept props, which are then passed to the provided slot function.

    return {};
  },
});
</script>

Expected Output (Rendered HTML):

<div class="card">
  <h2>User Information</h2>
  <div class="card-content">
    Alice (ID: 1)
  </div>
</div>

Explanation: The Card component receives a title. The parent provides a content slot. Crucially, the Card component exposes data (e.g., dataToPassToSlot) via the v-bind on the slot tag. The parent's slot function (implicitly defined by the template content using item) receives this data and renders it.

Example 2: Using a Render Function for the Slot

Parent Component (App.vue - TypeScript):

<template>
  <div>
    <Card title="Product Details">
      <template #content="slotProps">
        <ProductDisplay :product="slotProps.product" />
      </template>
    </Card>
  </div>
</template>

<script lang="ts">
import { defineComponent, h, PropType, computed } from 'vue';
import Card from './Card.vue';
import ProductDisplay from './ProductDisplay.vue'; // A separate component

interface Product {
  id: number;
  name: string;
  price: number;
}

export default defineComponent({
  components: {
    Card,
    ProductDisplay,
  },
  setup() {
    const productData: Product = { id: 101, name: 'Laptop', price: 1200 };

    // The `content` slot here directly receives `productData`
    // via the `product` prop bound to the slot.
    return {
      productData,
    };
  },
});
</script>

Card.vue (TypeScript - Render Function approach):

<template>
  <div class="card">
    <h2>{{ title }}</h2>
    <div class="card-content">
      <!--
        The slot component in Vue 3 can be used to render content.
        The key here is how we pass data to the slot.
        The `slots.content` itself is a function that can be called.
        We pass `product` as a prop to this slot function.
      -->
      <slot name="content" :product="productData"></slot>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, PropType, h } from 'vue';

interface Product {
  id: number;
  name: string;
  price: number;
}

export default defineComponent({
  props: {
    title: {
      type: String,
      required: true,
    },
  },
  setup(props) {
    // Data that the Card component wants to make available to the slot
    const productData: Product = { id: 101, name: 'Laptop', price: 1200 };

    return {
      productData,
    };
  },
});
</script>

ProductDisplay.vue (for demonstration):

<template>
  <div>
    <h3>{{ product.name }}</h3>
    <p>Price: ${{ product.price }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent, PropType } from 'vue';

interface Product {
  id: number;
  name: string;
  price: number;
}

export default defineComponent({
  props: {
    product: {
      type: Object as PropType<Product>,
      required: true,
    },
  },
});
</script>

Expected Output (Rendered HTML):

<div class="card">
  <h2>Product Details</h2>
  <div class="card-content">
    <div>
      <h3>Laptop</h3>
      <p>Price: $1200</p>
    </div>
  </div>
</div>

Explanation: The Card component passes productData to the content slot via :product="productData". The parent component's slot template then uses this product prop (slotProps.product) and passes it to a ProductDisplay component. This demonstrates passing complex data structures and using them within the slot's rendered output.

Constraints

  • Vue.js 3 is required.
  • TypeScript must be used for all component definitions.
  • The Card component should be a standard .vue file component.
  • The functional slot itself will be implemented using the <template #slotName="..." syntax.
  • Focus on the mechanism of passing data from the child (Card) to the parent's slot function and rendering that function's output within the child.

Notes

  • This challenge requires understanding how Vue's slot API works, specifically how to bind data to slots that are then accessible to the parent's template.
  • Think about how the setup function in Vue 3 can be used to prepare data that will be exposed to the template, including slots.
  • The "functional slot" aspect refers to the ability for the parent to provide a function-like structure (via template content that uses bound props) that the child component can invoke or render. Vue handles the underlying VNode creation.
  • Consider the slots object available in the setup function's second argument. While we are not directly calling a slot function from Card's setup in this specific implementation (Vue handles rendering the template), understanding slots is crucial for advanced slot manipulation. The key is the v-bind on the <slot> tag in the template of Card.vue.
Loading editor...
typescript