Image

vue-3-forms

6 min read
Last update: December 19, 2021

BaseRadio

The time has come to tackle the last of our form components for this course, the BaseRadio.

Radio buttons in HTML have a unique feature that we need to be aware of before we start building our component — they do not work as a single input, like a checkbox would. They live and function as part of a group of radio buttons that have a single state.

Depending on the group’s state, a radio button may be active or inactive in relation to those in its own group.

Due to this particular quirk in how radio buttons work, the BaseRadio component will also have another component to group them, the BaseRadioGroup.


First, the BaseRadio

We will begin the lesson by crafting our BaseRadio component. The goal is to have a flexible reusable component that wraps a single instance of a radio input, along with its label.

As before, we will first create our component file, BaseRadio.vue, inside the components folder. Then copy the input-label pair for one of our checkboxes from SimpleForm.vue and paste it into the component’s <template> block.

**📃BaseRadio.vue**

```html
<template>
    <input type="radio" v-model="event.pets" :value="1" name="pets" />
    <label>Yes</label>
</template>
```

Next, we’re going to first tackle the label. So let’s go ahead and create our `label` prop, and bind it to the `<label>` tag as we have done in previous lessons.

    **📃BaseRadio.vue**

    ```html
    <template>
        <input type="radio" v-model="event.pets" :value="1" name="pets" />
        <label v-if="label">{{ label }}</label>
    </template>

    <script>
        export default {
            props: {
                label: {
                    type: String,
                    default: ''
                }
            }
        }
    </script>
    ```

    Now that the label is dynamic and bound, let’s go ahead and make sure that our component can respond to `v-model` bindings.

    We will begin by creating our `modelValue` prop.

    **📃BaseRadio.vue**

    ```html
    <script>
        export default {
            props: {
                label: {
                    type: String,
                    default: ''
                },
                modelValue: {
                    type: [String, Number],
                    default: ''
                }
            }
        }
    </script>
    ```

    Notice that for the `BaseRadio` we have set the type of the `modelValue` prop to be of `[String, Number]`. This will tell Vue that this property can accept either Strings or Numeric values.

    When dealing with radio buttons, each button holds the value it represents in the set. For example, if you had a group of radio buttons to select your favorite pet, one radio button would represent `dog` and another would represent `cat`.

    It’s common for these buttons to also represent numerical values. For example, when selecting a number of guests for a hotel room, or even boolean values in their numeric format.

    Let’s now go ahead and bind our `modelValue` property to our input element. Similarly to checkboxes, radio buttons don’t bind to the `value` property, but use the `checked` property instead. However, in the particular case of radio buttons we need to check if this button is the one that is currently selected or not.

    Let’s go back to our favorite pet example. The `modelValue` of our `BaseRadio` elements will contain the **user’s preference**, so either `cat` or `dog` — but we need to be able to tell this radio which one of these values it represents.

    In order to do this, we’re going to add a new prop to the component, the `value`.

    **📃BaseRadio.vue**

    ```html
    <script>
        export default {
            props: {
                label: {
                    type: String,
                    default: ''
                },
                modelValue: {
                    type: [String, Number],
                    default: ''
                },
                value: {
                    type: [String, Number],
                    required: true
                }
            }
        }
    </script>
    ```

    Notice that we have not set a default value in the case of the `value` prop, but instead chose to set the property to `required`. If this property is not set, Vue will issue a warning for us.

    A radio button simply does not make sense when there’s not a value attached to it, and a default value could potentially be problematic if the user forgets to set it, and multiple radios end up having the same value.

    In order to know if our radio is `:checked`, we will look to see if the `modelValue` is equal to the `value`. That means that the user’s preference, “dog” for example, will either be equal to this radio’s value or won’t — checked, or unchecked.

    Let’s go ahead and remove the old `v-model` binding and the hard-coded `name` attribute, and replace it with our `checked` binding. We’ll also make sure to also update the `value` binding to our new prop.

    **📃BaseRadio.vue**

    ```html
    <template>
        <input type="radio" :checked="modelValue === value" :value="value" />
        <label v-if="label">{{ label }}</label>
    </template>

    <script>
        export default {
            props: {
                label: {
                    type: String,
                    default: ''
                },
                modelValue: {
                    type: [String, Number],
                    default: ''
                },
                value: {
                    type: [String, Number],
                    required: true
                }
            }
        }
    </script>
    ```

    Almost there! We now need to add the second part of our `v-model` contract, the emitting of update events. Radio buttons trigger `change` events **when they become the selected option**, so let’s set up a `change` event listener with our `update:modelValue` emit.

    **📃BaseRadio.vue**

    ```html
    <template>
        <input type="radio" :checked="modelValue === value" :value="value" @change="$emit('update:modelValue', value)" />
        <label v-if="label">{{ label }}</label>
    </template>
    ```

    Be sure to pay close attention to the payload of our `$emit`. We are going to emit the `value` prop. We want our `v-model` recipient on the parent to hold the value of the currently selected radio button, and because the `change` event will fire only when the element makes the selected choice, we can safely fire `value` to update the parent about the newly selected option.

    Finally, because we removed the hard-coded `name` attribute, we need to make sure that the developer using this component is able to set attributes like `name` into our input. So we will use `v-bind="$attrs"` on our input like we learned when building our `BaseInput` component, to be able to allow this attribute injection into the correct element.

    **📃BaseRadio.vue**

    ```html
    <template>
        <input type="radio" :checked="modelValue === value" :value="value" v-bind="$attrs" @change="$emit('update:modelValue', value)" />
        <label v-if="label">{{ label }}</label>
    </template>

    <script>
        export default {
            props: {
                label: {
                    type: String,
                    default: ''
                },
                modelValue: {
                    type: [String, Number],
                    default: ''
                },
                value: {
                    type: [String, Number],
                    required: true
                }
            }
        }
    </script>
    ```

    ---

    ## Updating the demo form

    Now that our component is ready, we can go back to our `SimpleForm.vue`, and replace the two checkboxes with our shiny new `BaseRadio` component!

    **📃SimpleForm.vue**

    ```html
    <h3>Are pets allowed?</h3>
    <div>
        <BaseRadio v-model="event.pets" :value="1" label="Yes" name="pets" />
    </div>

    <div>
        <BaseRadio v-model="event.pets" :value="0" label="No" name="pets" />
    </div>
    ```

    ---

    ## Wrapping up

    As we’ve seen, radio buttons have a few quirks that can potentially make them confusing and hard to work with, but we’ve made great progress in creating a solid `BaseRadio` component. In our next lesson we’re going to take this a step further and create the `BaseRadioGroup` component, which will allow us to simplify the setting up and creation of radio button groups even further.

    See you there!