Hone logo
Hone
Problems

Mastering Scoped Slots in Vue.js

Scoped slots are a powerful feature in Vue.js that allow child components to pass data back to their parent components, enabling more flexible and reusable component design. This challenge will test your understanding of how to implement and utilize scoped slots to create dynamic and data-rich UI elements.

Problem Description

Your task is to create a reusable ItemList component in Vue.js using TypeScript that displays a list of items. The parent component should be able to customize how each individual item is rendered, including displaying specific data properties of the item and potentially adding interactive elements.

Key Requirements:

  1. ItemList Component:

    • Accepts an array of items as a prop. Each item in the array will be an object with at least id (number) and name (string) properties.
    • Must use a scoped slot to render each individual item.
    • The scoped slot should expose the current item being rendered to the parent.
    • The component should iterate over the items prop and render each one using the provided scoped slot.
  2. Parent Component (Usage Example):

    • Will provide an array of items to the ItemList component.
    • Will utilize the scoped slot to define how each item is displayed.
    • The parent's template should demonstrate passing data back from the slot (e.g., handling a click event on an item).

Expected Behavior:

When the ItemList component is used, each item in the provided items array should be rendered according to the template defined in the parent component's slot. The parent should have access to the individual item data within the slot scope.

Edge Cases to Consider:

  • An empty items array should result in nothing being rendered by the ItemList component (no errors).

Examples

Example 1:

ItemList Component (Simplified for clarity):

<template>
  <div>
    <div v-for="item in items" :key="item.id">
      <slot name="item" :item="item"></slot>
    </div>
  </div>
</template>

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

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

export default defineComponent({
  props: {
    items: {
      type: Array as () => Item[],
      required: true,
    },
  },
});
</script>

Parent Component Usage:

<template>
  <div>
    <h2>My Items</h2>
    <ItemList :items="myItems">
      <template #item="{ item }">
        <div @click="handleItemClick(item)">
          {{ item.name }} (ID: {{ item.id }})
        </div>
      </template>
    </ItemList>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import ItemList from './ItemList.vue';

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

export default defineComponent({
  components: {
    ItemList,
  },
  data() {
    return {
      myItems: [
        { id: 1, name: 'Apple' },
        { id: 2, name: 'Banana' },
        { id: 3, name: 'Cherry' },
      ] as Item[],
    };
  },
  methods: {
    handleItemClick(item: Item) {
      alert(`Clicked on: ${item.name}`);
    },
  },
});
</script>

Output (Rendered HTML):

<div>
  <h2>My Items</h2>
  <div>
    <div>
      <div onclick="alert('Clicked on: Apple')">
        Apple (ID: 1)
      </div>
    </div>
    <div>
      <div onclick="alert('Clicked on: Banana')">
        Banana (ID: 2)
      </div>
    </div>
    <div>
      <div onclick="alert('Clicked on: Cherry')">
        Cherry (ID: 3)
      </div>
    </div>
  </div>
</div>

Explanation: The ItemList component iterates through myItems. For each item, it renders the content provided by the #item slot in the parent. The item data is made available within the slot scope, allowing the parent to display item.name and item.id, and to attach an onclick handler that receives the item object.

Example 2: Handling an Empty List

ItemList Component: (Same as Example 1)

Parent Component Usage:

<template>
  <div>
    <h2>Empty List Example</h2>
    <ItemList :items="emptyItems">
      <template #item="{ item }">
        <div>{{ item.name }}</div>
      </template>
    </ItemList>
    <p v-if="emptyItems.length === 0">No items to display.</p>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import ItemList from './ItemList.vue';

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

export default defineComponent({
  components: {
    ItemList,
  },
  data() {
    return {
      emptyItems: [] as Item[],
    };
  },
});
</script>

Output (Rendered HTML):

<div>
  <h2>Empty List Example</h2>
  <p>No items to display.</p>
</div>

Explanation: When items is an empty array, the v-for loop inside ItemList does not execute, and no slot content is rendered. The parent component can conditionally render a message if there are no items.

Constraints

  • The ItemList component should only use a single scoped slot named item.
  • The items prop will always be an array of objects. Each object will have at least id (number) and name (string) properties.
  • The solution should be implemented using Vue 3 and TypeScript.
  • Focus on the concept of scoped slots; complex styling or advanced Vue features are not required.

Notes

  • Remember to define the Item interface for type safety.
  • Think about how the parent component can receive data from the slot. This is the core of scoped slots.
  • Consider how you would provide additional data to the slot if needed, beyond just the item itself. (This is a hint for further exploration, not a strict requirement for this challenge).
Loading editor...
typescript