router icon indicating copy to clipboard operation
router copied to clipboard

Component guards not working when defined on mixins

Open AgileInteractive opened this issue 5 years ago • 24 comments

Version

4.0.0-beta.9

Reproduction link

https://jsfiddle.net/ukeagtjm/

Steps to reproduce

const Mixin = {
    beforeRouteEnter(to, from, next) {
        console.log('****** Before route enter (mixin)');
        next();
    }
};

...

mixins:[Mixin]

What is expected?

beforeRouteEnter() to run and log from mixin

What is actually happening?

beforeRouteEnter() is never executed

AgileInteractive avatar Sep 07 '20 13:09 AgileInteractive

It wasn't intentionally removed but I will have to check a way to apply the mixins from components and its extends option

posva avatar Sep 07 '20 14:09 posva

the same for beforeRouteUpdate and beforeRouteLeave events

dmitrystas avatar Oct 03 '20 22:10 dmitrystas

On the same topic has anything changed with beforeEnter because I do a simple console log, it's hitting it x2 and also doesn't work properly with Vuex getters anymore, define a const within beforeEnter = game over.

n10000k avatar Nov 17 '20 00:11 n10000k

Are there any possibilities or some workaround for solving this issue?

webigorkiev avatar Jun 30 '21 11:06 webigorkiev

Router hooks like beforeRouteLeave, beforeRouteUpdate, beforeRouteEnter do not resolve in this. $options. This can be fixed if vue-router provides a strategy for resolveMergedOptions (internalOptionMergeStrats)

app.config.optionMergeStrategies.beforeRouteEnter = mergeAsArray; app.config.optionMergeStrategies.beforeRouteUpdate = mergeAsArray; app.config.optionMergeStrategies.beforeRouteLeave= mergeAsArray;

If you do this in this.$options everything resolves, but still doesn't work (only the method from the component is executed, not from the mixin)

webigorkiev avatar Jun 30 '21 12:06 webigorkiev

Bummer! Ran into this issue today as well. My use-case is to have my in-component guards placed into a page mixin for easy reuse across multiple similar page components.

This worked just fine in vue router 3. Either this is simply a regression or such a use case is no longer supported? Seems odd that it would be the latter case when mixins are still a supported feature in vue 3?

BARNZ avatar Jul 27 '21 07:07 BARNZ

Also experiencing the same issue. Moreover, seems to be beforeRouteEnter, etc also not working at component level.

For example using in App.vue console output is empty

...
  data: () => ({}),
  beforeRouteEnter(from, to, next) {
    console.log('beforeRouteEnter') // not getting triggered
    next()
  },
  computed: {
...

dpmango avatar Aug 05 '21 11:08 dpmango

@dpmango That's not supposed to work. Route guards only work when used in route components, not just any components.

LinusBorg avatar Aug 05 '21 14:08 LinusBorg

Will the problem be fixed?

TothingWay avatar Sep 23 '21 02:09 TothingWay

You can use my solution step one:copy this code as a javascript file

mergeRouterGuard.js

export const NeedMergeGuards = ['beforeRouteEnter', 'beforeRouteUpdate', 'beforeRouteLeave']

export function mergeFuncUtil (nextHook, prevHook) {
    return function (to, from, next) {
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        const ctx = this
        function reduceNext () {
            return prevHook.bind(ctx)(to, from, next)
        }
        return nextHook.bind(ctx)(to, from, reduceNext)
    }
}
/** execution order:  the guard in component is executed last one
 * @param distOption
 * {
        name: 'test',
        mixins: [myMixin],
        beforeRouteEnter (to, from, next) { next(this => {}) },
        beforeRouteUpdate (to: any, from: any, next: Function) { next() },
        beforeRouteLeave (to, from, next) { next() }}
    }
 * @param customMixins
    [
        {
            beforeRouteEnter: Function
        },
        {
            beforeRouteEnter: Function
        }
    ]
 * @param needMergeHooks ['beforeRouteEnter', 'beforeRouteUpdate', 'beforeRouteLeave']
 * @returns
 * {
        name: 'test',
        mixins: [myMixin],
        beforeRouteEnter (to, from, next) { allNext(this => {}) },
        beforeRouteUpdate (to, from, next) { allNext() },
        beforeRouteLeave (to, from, next) { allNext() }
 * }
 */
export const mergeRouterGuard = (
    distOption,
    customMixins,
    needMergeGuards = NeedMergeGuards
) => {
    needMergeGuards.forEach((mergeGuard) => {
        const customMergeGuards = customMixins
            .filter(customMixin => customMixin[mergeGuard])
            .map(customMixin => customMixin[mergeGuard])

        if (customMergeGuards.length > 0) {
            const customFunc = customMergeGuards.reduce(function (mergeFunc, customMergeGuard) {
                const fn = mergeFuncUtil(mergeFunc, customMergeGuard)
                return fn
            })
            const finalHook = !distOption[mergeGuard] ? customFunc
                : mergeFuncUtil(customFunc, distOption[mergeGuard])

            distOption[mergeGuard] = function (...args) {
                return finalHook.bind(this)(...args)
            }
        }
    })

    return distOption
}

step two: import mergeRouterGuard into component

step three: use in component, give an example

Test.vue

<script lang="ts">
import { defineComponent } from 'vue'
import { myMixin } from './myMixin'
import { myMixin2 } from './myMixin2'
import { mergeRouterGuard } from '../mergeRouterGuard'

const mixins = [ myMixin, myMixin2 ]

// vue option
const option = {
    name: 'test',
    data () {
        return {
            msg: 'test'
        }
    },
    mixins:,           //  important !!!!!!!,  mergeRouteGuard only merge router guard
    beforeRouteEnter (to, from, next) {
        console.log('beforeRouteEnter')
        next((...args) => console.log(args))
    },
    beforeRouteUpdate (to, from, next) {
        console.log('---test.vue---------from test.vue-----test.vue-----')
        console.log(this)
        next((...args) => console.log(args))
    },
    beforeRouteLeave (to, from, next) {
        console.log('beforeRouteLeave')
        next((...args) => console.log(args))
    }
}
// merge router guard
const mergeRouterGuardOption = mergeRouterGuard(option, mixins)

export default defineComponent(mergeRouterGuardOption)
</script>

if you has bug, can connect me

jsweber avatar Nov 18 '21 16:11 jsweber

Go to 666 car

Thanayaby avatar Nov 20 '21 18:11 Thanayaby

Hey everybody, I found a solution. I hope it helps everybody out. In your mixin, you can create a function called created which will run immediately after the component is created. Then, you can add in the hook by inspecting the $route variable on this:

const mixin = {
    created(){
        let route = this.$route.matched[0];
        if(!route) 
            return;
        route.leaveGuards = new Set([function(to, from, next){ 
            console.log("to", to, "from", from, "next", next);
            next();
        }]);
    }
}

turbobuilt avatar Dec 30 '21 21:12 turbobuilt

Now that Vue3 is now the default, would someone from the vue-router core team be willing to speak on this issue? This seems to be the only breaking change mentioned in the migration guide with no reasonable upgrade path. The migration guide even links to this open issue to "track its support". It seems some people on this thread have found reasonable workarounds, but will support ever added to vue-router core?

Also, its okay to say "no we wont support it". That's a fine response, but it would be great to get clarity and update the migration guide to mention this.

darrinmn9 avatar Feb 10 '22 01:02 darrinmn9

Faced same issue, i use vue-property-decorator and firstly thought that issue lies in it. Btw, fixed it moving logic to another function in mixin that invokes navigation guard from component.

// Component
 beforeRouteLeave(to, from, next) {
   this.onLeaveGuard(to, from, next)
 }
 // Mixin
 onLeaveGuard(to, from, next) {
    console.log('mixin hook');
    if (this.isDataChanged) {
      this.toggleOnLeaveModalVisibility({ to, next });
    } else {
      next()
    }
  }

tsiotska avatar Feb 16 '22 16:02 tsiotska

If the goal of using mixins is to extract the logic of your navigation guards, maybe this library can serve as a workaround : https://github.com/abellion/vue-router-shield (disclaimer : I'm the creator of this library)

abellion avatar Apr 12 '22 10:04 abellion

The workaround we found was to import the mixin with a spread operator instead. This method requires only a single line change in the components that use the mixin.

navigationGuardsMixin.js

export default {
    beforeRouteEnter(to, from, next) {
        next(vm => {
            // Your logic that has access to the component instance
        });
    },
    beforeRouteUpdate(to, from, next) {
        // Your logic
        next();
    },
    beforeRouteLeave(to, from, next) {
        // Your logic
        next();
    }
}

YourComponent.vue

import navigationGuardsMixin from '@/mixins/navigationGuardsMixin';

export default {
    mixins: [navigationGuardsMixin], // old (remove this line)
    ...navigationGuardsMixin,        // new (add this line)
    // The rest of your component here
};

leboeuf avatar Aug 31 '22 14:08 leboeuf