Search code examples
vue.jsvuejs2vue-component

Conditional <router-link> in Vue.js dependant on prop value?


Hopefully this is a rather simple question / answer, but I can't find much info in the docs.

Is there a way to enable or disable the anchor generated by <router-link> dependent on whether a prop is passed in or not?

<router-link class="Card__link" :to="{ name: 'Property', params: { id: id }}">
  <h1 class="Card__title">{{ title }}</h1>
  <p class="Card__description">{{ description }}</p>
</router-link>

If there's no id passed to this component, I'd like to disable any link being generated.

Is there a way to do this without doubling up the content into a v-if?

Thanks!


Solution

  • I sometimes do stuff like this:

    <component
        :is="hasSubLinks ? 'button' : 'router-link'"
        :to="hasSubLinks ? undefined : href"
        :some-prop="computedValue"
        @click="hasSubLinks ? handleClick() : navigate"
    >
        <!-- arbitrary markup -->
    </component>
    
    ...
    
    computed: {
        computedValue() {
            if (this.hasSubLinks) return 'something';
            if (this.day === 'Friday') return 'tgif';
            return 'its-fine';
        },
    },
    

    But I basically always wrap router-link, so you can gain control over disabled state, or pre-examine any state or props before rendering the link, with something like this:

    <template>
        <router-link
            v-slot="{ href, route, navigate, isActive, isExactActive }"
            :to="to"
        >
            <a
                :class="['nav-link-white', {
                    'nav-link-white-active': isActive,
                    'opacity-50': isDisabled,
                }]"
                :href="isDisabled ? undefined : href"
                @click="handler => handleClick(handler, navigate)"
            >
                <slot></slot>
            </a>
    
        </router-link>
    </template>
    
    <script>
    export default {
        name: 'top-nav-link',
    
        props: {
            isDisabled: {
                type: Boolean,
                required: false,
                default: () => false,
            },
    
            to: {
                type: Object,
                required: true,
            },
        },
    
        data() {
            return {};
        },
    
        computed: {},
    
        methods: {
            handleClick(handler, navigate) {
                if (this.isDisabled) return undefined;
                return navigate(handler);
            },
        },
    
    };
    </script>
    

    In my app right now, I'm noticing that some combinations of @click="handler => handleClick(handler, navigate)" suffer significantly in performance.

    For example this changes routes very slow:

    @click="isDisabled ? undefined : handler => navigate(handler)"
    

    But the pattern in my full example code above works and has no performance issue.

    In general, ternary operator in @click can be very dicey, so if you get issues, don't give up right away, try many different ways to bifurcate on predicates or switch over <component :is="" based on state. navigate itself is an ornery one because it requires the implicit first parameter to work.

    I haven't tried, but you should be able to use something like Function.prototype.call(), Function.prototype.apply(), or Function.prototype.bind().

    For example, you might be able to do:

    @click="handler => setupNavigationTarget(handler, navigate)"
    
    ...
    
    setupNavigationTarget(handler, cb) {
        if (this.isDisabled) return undefined;
        return this.$root.$emit('some-event', cb.bind(this, handler));
    },
    
    ...
    
    // another component
    mounted() {
        this.$root.$on('some-event', (navigate) => {
            if (['Saturday', 'Sunday'].includes(currentDayOfTheWeek)) {
                // halt the navigation event
                return undefined;
            }
    
            // otherwise continue (and notice how downstream logic
            // no longer has to care about the bound handler object)
            return navigate();
        });
    },