Skip to content

Commit 217adc6

Browse files
authored
Allow guards to return instead of calling the next callback (#187)
* initial version * Update active-rfcs/0000-router-return-guards.md * prepare for merge
1 parent 33c48c5 commit 217adc6

File tree

1 file changed

+110
-0
lines changed

1 file changed

+110
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
- Start Date: 2020-06-08
2+
- Target Major Version: Vue Router 3 and 4
3+
- Reference Issues: https://github.com/vuejs/rfcs/issues/177
4+
- Implementation PR: https://github.com/vuejs/vue-router-next/pull/343
5+
6+
# Summary
7+
8+
Allow navigation guards to return the value **or a Promise** of it instead of calling `next`:
9+
10+
```js
11+
// change
12+
router.beforeEach((to, from, next) => {
13+
if (!isAuthenticated) next(false)
14+
else next()
15+
})
16+
17+
// into
18+
router.beforeEach(() => isAuthenticated)
19+
```
20+
21+
Since we can check the amount or arguments the navigation guard has, we can automatically know if we should look at the returned value or not. **This is not a breaking change**.
22+
23+
# Motivation
24+
25+
- Avoid forgetting to call `next`
26+
- Avoid the problem of calling `next` multiple times (since we can only return once)
27+
- `return` is more idiomatic since it can only be called once
28+
- Avoid the need of 3 arguments in the function if they are not all used
29+
30+
# Detailed design
31+
32+
If 2 arguments are passed to a navigation guard, Vue Router will look at the returned value as if it was passed to `next`.
33+
34+
## Validate/cancel a navigation
35+
36+
To validate a navigation, you currently have to call `next()` or `next(true)`. Instead, you can not return anything (same as explicitly returning `undefined`) or return `true`. To cancel the navigation you could call `next(false)`, now you can explicitly `return false`:
37+
38+
```js
39+
router.beforeEach((to) => {
40+
if (to.meta.requiresAuth && !isAuthenticated) return false
41+
})
42+
43+
// with async / await
44+
router.beforeEach(async (to) => {
45+
return await canAccessPage(to)
46+
})
47+
```
48+
49+
## Redirect to a different location
50+
51+
We can redirect to a location by returning the same kind of object passed to `router.push`:
52+
53+
```js
54+
router.beforeEach((to) => {
55+
if (to.meta.requiresAuth && !isAuthenticated)
56+
return {
57+
name: 'Login',
58+
query: {
59+
redirectTo: to.fullPath,
60+
},
61+
}
62+
})
63+
64+
// with async / await
65+
router.beforeEach(async (to) => {
66+
if (!(await canAccessPage(to))) {
67+
return {
68+
name: 'Login',
69+
query: {
70+
redirectTo: to.fullPath,
71+
},
72+
}
73+
}
74+
})
75+
```
76+
77+
## Errors
78+
79+
Unexpected errors can still be thrown synchronously or synchronously:
80+
81+
```js
82+
router.beforeEach((to) => {
83+
throw new Error()
84+
})
85+
86+
// with async / await
87+
router.beforeEach(async (to) => {
88+
throw new Error()
89+
})
90+
91+
// with promises
92+
router.beforeEach((to) => {
93+
return Promise.reject(new Error())
94+
})
95+
```
96+
97+
# Drawbacks
98+
99+
- Vue Router 3 might preset a higher implementation cost
100+
101+
# Alternatives
102+
103+
- This could be implemented with a function helper but the idea is to shift the way we write navigation guards.
104+
105+
What other designs have been considered? What is the impact of not doing this?
106+
107+
# Adoption strategy
108+
109+
- Add both syntaxes to Vue Router 4 (https://github.com/vuejs/vue-router-next/pull/343/files)
110+
- A codemod should be able to handle the conversion even though it's not a breaking change

0 commit comments

Comments
 (0)