Hone logo
Hone
Problems

Vue Radio Button Group Binding

This challenge focuses on implementing a common UI pattern in Vue.js: creating a group of radio buttons where only one can be selected at a time, and the selected value is easily accessible and manageable through Vue's data binding. This is a fundamental building block for many forms and interactive elements.

Problem Description

Your task is to create a Vue component that renders a group of radio buttons. This component should allow a parent component to control which radio button is currently selected and to receive updates when the user changes the selection.

Key Requirements:

  1. Component Structure: Create a Vue component (e.g., RadioButtonGroup.vue) that accepts a list of options for the radio buttons and a prop to represent the currently selected value.
  2. Rendering Radio Buttons: The component should dynamically render a set of <input type="radio"> elements based on the provided options. Each radio button should have a unique value and a name attribute (all radio buttons in the group should share the same name).
  3. v-model Integration: The component must be designed to work seamlessly with Vue's v-model directive. This means it should accept a modelValue prop and emit an update:modelValue event when the selection changes.
  4. Option Representation: Each option should likely be an object containing at least a label (what the user sees) and a value (the underlying data associated with the option).
  5. Selected State: The radio button corresponding to the modelValue prop should be checked.
  6. User Interaction: When a user clicks on a radio button, the component should emit an update:modelValue event with the value of the newly selected radio button.

Expected Behavior:

When used in a parent component with v-model, the RadioButtonGroup should:

  • Display a set of radio buttons.
  • Have the radio button whose value matches the parent's bound variable pre-selected.
  • Update the parent's bound variable when the user clicks a different radio button.

Edge Cases:

  • What happens if no options are provided?
  • What happens if the modelValue prop is initially null or undefined?
  • Ensure that all radio buttons within a group share the same name attribute for proper browser behavior.

Examples

Example 1: Basic Usage

Parent Component (App.vue):

<template>
  <div>
    <RadioButtonGroup v-model="selectedFruit" :options="fruitOptions" />
    <p>Selected Fruit: {{ selectedFruit }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';
import RadioButtonGroup from './components/RadioButtonGroup.vue';

export default defineComponent({
  components: {
    RadioButtonGroup,
  },
  setup() {
    const selectedFruit = ref('banana'); // Initial selection
    const fruitOptions = ref([
      { label: 'Apple', value: 'apple' },
      { label: 'Banana', value: 'banana' },
      { label: 'Cherry', value: 'cherry' },
    ]);

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

RadioButtonGroup.vue (Conceptual Structure):

<template>
  <div v-for="option in options" :key="option.value">
    <input
      type="radio"
      :id="option.value"
      :value="option.value"
      :name="groupName" // A unique name for this group
      :checked="modelValue === option.value"
      @change="$emit('update:modelValue', option.value)"
    />
    <label :for="option.value">{{ option.label }}</label>
  </div>
</template>

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

export default defineComponent({
  props: {
    modelValue: {
      type: [String, Number, null], // Allow for different value types, or no initial selection
      required: true,
    },
    options: {
      type: Array,
      required: true,
      validator: (value: any[]) => value.every(opt => typeof opt.label === 'string' && opt.hasOwnProperty('value')),
    },
  },
  emits: ['update:modelValue'],
  setup(props) {
    // A generated name to ensure radio buttons are grouped correctly
    const groupName = computed(() => `radio-group-${Math.random().toString(36).substring(7)}`);

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

Output in Parent:

Selected Fruit: banana

When the user clicks "Apple", the output should change to:

Selected Fruit: apple

Example 2: No Initial Selection

Parent Component (App.vue):

<template>
  <div>
    <RadioButtonGroup v-model="selectedColor" :options="colorOptions" />
    <p>Selected Color: {{ selectedColor || 'None' }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';
import RadioButtonGroup from './components/RadioButtonGroup.vue';

export default defineComponent({
  components: {
    RadioButtonGroup,
  },
  setup() {
    const selectedColor = ref(null); // No initial selection
    const colorOptions = ref([
      { label: 'Red', value: 'red' },
      { label: 'Green', value: 'green' },
      { label: 'Blue', value: 'blue' },
    ]);

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

Output in Parent:

Selected Color: None

When the user clicks "Green", the output should change to:

Selected Color: green

Constraints

  • The options prop will be an array of objects. Each object will have a label (string) and a value (string, number, or null).
  • The modelValue prop can be a string, number, or null.
  • The component should render at least one radio button if options are provided.
  • Performance is not a critical concern for this challenge, but avoid excessively inefficient rendering or event handling.

Notes

  • Consider how to manage the name attribute for the radio buttons to ensure they are grouped correctly by the browser. A dynamically generated name or a passed-in prop could work.
  • Remember that Vue 3 uses the modelValue prop and update:modelValue event for v-model by default.
  • Think about the accessibility of your radio buttons (e.g., using label elements correctly with for attributes).
  • The validator in the options prop is a good practice for ensuring data integrity.
Loading editor...
typescript