Hone logo
Hone
Problems

Vue 3: Accessing Child Component Instances with Template Refs

This challenge focuses on a fundamental Vue 3 feature: using template refs to gain direct access to child component instances. Understanding how to do this is crucial for advanced component interaction, imperative DOM manipulation when necessary, and integrating with third-party libraries that require direct DOM or component access.

Problem Description

Your task is to create a parent Vue component that dynamically renders a list of child components. The parent component needs to be able to access the instance of a specific child component when a button within the parent is clicked. This interaction will involve using Vue's template ref system.

Requirements:

  1. Parent Component (ParentComponent):

    • It should accept an array of data (e.g., strings) as a prop. Each item in this array will represent data for a child component.
    • It should render a ChildComponent for each item in the data array.
    • It should display a button labeled "Focus First Child".
    • When the "Focus First Child" button is clicked, the ParentComponent should call a method on the first rendered ChildComponent instance.
  2. Child Component (ChildComponent):

    • It should accept a single string prop, text.
    • It should have a simple template that displays the text prop.
    • It must expose a public method, highlight, which can be called from the parent. This method should visually indicate that it has been called (e.g., by changing the background color of its root element).
  3. Template Refs:

    • You will need to use the ref attribute in the ParentComponent's template to obtain a reference to the ChildComponent instances.
    • Consider how to handle multiple child components and get a reference specifically to the first one.

Expected Behavior:

When the "Focus First Child" button is clicked, the highlight method of the first ChildComponent in the list should be executed, visually changing its appearance. If there are no child components rendered, clicking the button should do nothing.

Edge Cases:

  • No Child Components: The parent component should gracefully handle cases where the data array is empty, meaning no child components are rendered. The "Focus First Child" button should be present, but clicking it should not result in an error.
  • Dynamic List: While not strictly required for this challenge, consider how your solution would adapt if the list of child components were dynamically added or removed. (Your solution should work for a static list as per the requirements).

Examples

Example 1:

ParentComponent.vue (Simplified):

<template>
  <div>
    <button @click="focusFirstChild">Focus First Child</button>
    <ChildComponent v-for="(item, index) in items" :key="index" :text="item" :ref="el => childRefs[index] = el" />
  </div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue';
import ChildComponent from './ChildComponent.vue';

const props = defineProps<{ items: string[] }>();
const childRefs = reactive<any[]>([]); // Array to hold refs for multiple children

const focusFirstChild = () => {
  if (childRefs.length > 0 && childRefs[0]) {
    childRefs[0].highlight();
  }
};
</script>

ChildComponent.vue (Simplified):

<template>
  <div class="child">
    {{ text }}
  </div>
</template>

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

const props = defineProps<{ text: string }>();

const highlight = () => {
  // Implementation to visually highlight the element
  console.log(`Highlighting: ${props.text}`);
  // In a real scenario, you'd manipulate the DOM or add a class
  const el = document.querySelector('.child') as HTMLElement; // This is a simplified way for demo
  if (el) {
    el.style.backgroundColor = 'yellow';
  }
};

defineExpose({ highlight });
</script>

<style scoped>
.child {
  border: 1px solid black;
  padding: 10px;
  margin-bottom: 5px;
}
</style>

Input:

ParentComponent is rendered with items = ['Apple', 'Banana', 'Cherry'].

Expected Output (Visual):

Initially, the three ChildComponent instances are displayed. When the "Focus First Child" button is clicked, the ChildComponent displaying "Apple" should have its background color changed to yellow.

Explanation:

The ParentComponent iterates through its items prop, rendering a ChildComponent for each. A ref is dynamically assigned to each ChildComponent instance using a function el => childRefs[index] = el. When the button is clicked, focusFirstChild accesses the first element in the childRefs array (childRefs[0]) and calls its highlight method.

Example 2:

Input:

ParentComponent is rendered with an empty items prop (items = []).

Expected Output (Visual/Behavior):

The "Focus First Child" button is displayed. Clicking the button has no effect, and no errors occur.

Explanation:

The childRefs array will be empty. The if (childRefs.length > 0 && childRefs[0]) condition in focusFirstChild prevents any attempt to access childRefs[0], thus avoiding errors.

Constraints

  • Vue.js version: 3.x
  • Language: TypeScript
  • The highlight method in ChildComponent must be explicitly exposed using defineExpose.
  • The solution should be performant, avoiding unnecessary re-renders or DOM queries where possible.
  • Use Composition API for both components.

Notes

  • When using v-for with template refs, you'll often end up with an array of references. Think about how to manage this array and access specific elements within it.
  • The ref attribute can accept a string (for Options API) or a function (for Composition API) or a ref object (Composition API, typically for DOM elements). For accessing child component instances in Composition API with v-for, a function ref is a common and effective pattern.
  • Remember to properly type your refs where appropriate. For this challenge, any[] is acceptable for childRefs to simplify the focus on template ref usage, but in a production scenario, you'd aim for more specific typing.
  • The highlight method's visual indication can be as simple as changing a CSS style directly for this exercise.
Loading editor...
typescript