How can I avoid duplicating props across similar Vue components?

Viewed 119

Component Structure:

component structure

I have many button components in my application and each one has a different icon within it (I've placed these icons in their own components so I can use them elsewhere). I would like these icon components to respond to hover events when the parent component is hovered over.

I have a solution, but there's repetition in it. For every button (SettingsButton, HelpButton etc.) I find myself writing the hover code in order to pass it to the icon component. Is there a way to avoid this?

Click here for JS fiddle example.

const { ref } = Vue;

const HelpIcon = {
    template: `<div :style="{ 'background': hover ? 'blue' : '' }"></div>`,

    props: {
        hover: {
            type: Boolean,
            required: true,
        },
    },
};

const BaseButton = {
    template: `
        <a href="/" @click.prevent>
            <slot name="icon" />

            <span>
                <slot name="text" />
            </span>
        </a>
    `,
};

const HelpButton = {
    components: { BaseButton, HelpIcon },

    // _every_ button has to have `mouseover`, `mouseleave` and `hover` ref
    template: `
        <BaseButton @mouseover="hover = true" @mouseleave="hover = false">
            <template #icon>
                <HelpIcon :hover="hover" />
            </template>

            <template #text>Help</template>
        </BaseButton>
    `,

    setup() {
        // _every_ button has this ref
        const hover = ref(false);
        return { hover };
    }
};

Vue.createApp({
    components: { HelpButton },
}).mount('#app');
1 Answers

You could use a mixin like :

const hoverable={
    props: {
        hover: {
            type: Boolean,
            required: true,
        },
    },   
}

then add it to your icon mixins option :

const HelpIcon = {
    template: `<div :style="{ 'background': hover ? 'blue' : '' }"></div>`,
     mixins:[hoverable]
};

Try also to register the events handler in BaseButton then pass the hover via scoped slot :

const BaseButton = {
    template: `
        <a href="/" @click.prevent  @mouseover="hover = true" @mouseleave="hover = false">
            <slot name="icon" :hover="hover" />
          
            <span>
                <slot name="text" />
            </span>
        </a>
    `,
 setup() {
        const hover = ref(false);
        return { hover };
    }
};

then :

 <BaseButton >
            <template #icon="{hover}" >
                <HelpIcon :hover="hover" />
            </template>

            <template #text>Help</template>
 </BaseButton>

LIVE DEMO

Related