Axios and vue-resource put method doesn't work - vue.js

I have on front vuejs and on backend java. In one component I bring the users from the database.
Users.vue
getUsers() {
this.$http.get("/user")
.then((response) => {
this.users = response.data.users;
})
}
Still here I use v-for to bring all users in another component User.vue.
<app-user v-for="user in users" :user="user" :key="user.index"></app-user>
In user component I have a router link that takes me to another page where I can edit the username.
User.vue
<p class="user-name">{{user.firstName}}</p>
<router-link :to="'/users/edit-user/'+user.id">
<a ><i class="ti-pencil-alt" aria-hidden="true"></i></a>
</router-link>
EditUser.vue
<template>
<input type="text" :value="user.firstName" v-model="userInfo.firstName">
</template>
<script>
export default {
data() {
return {
user: {},
userInfo: {
firstName: '',
}
}
},
created() {
this.getUsers();
},
methods: {
getUsers() {
this.$http.get("/user/" + this.$route.params.id)
.then((response) => {
this.user = response.data;
})
},
updateUser() {
axios.put('/user', this.userInfo,
{'headers':{'X-AUTH-TOKEN':localStorage.token}},
{'headers':{'Content-Type': 'application/json'}})
.then((response) => {
console.log("Success! You edited the user");
})
.catch((response) => {
console.log('Error in edit');
})
}
},
}
</script>
I started learning vuejs a month ago and still have to learn :).
In the input I use :value="user.firstName" to bring the value for firstName that already exists. I try to use v-model="userInfo.firstName" to get new value for userName, but when I put this, the value, that existed already, disappears from the input.
To save data with post works fine but only with axios. I don't know why post doesn't work with vue-resource. So I tried put with axios too, but what I edit when I press save button, on EditUser.vue, my request doesn't go to server.
I say this because I saw that in the backend I don't get any error, nothing, but if I use post or get I can get or save users.
What do I do wrong in my code that I don't edit the user?

There seems to be nothing wrong with your axios implementation. Seems to me that you are updating to the general /users/ route. This works for post, since no user id exists yet, so a new one will be created. However, if you use PUT, a user id does exist. So I think you should modify your endpoint to be /user/:userid instead of just /user. Hopes this helps.
From restapitutorial dot com/lessons/httpmethods.html:
Examples:
POST http://www.example.com/customers
PUT http://www.example.com/customers/12345

Related

Nuxt 3 JWT authentication using $fetch and Pinia

I'm discovering Nuxt 3 since a few days and I'm trying to do a JWT authentication to a distinct API.
As #nuxtjs/auth-next doesn't seem to be up to date and as I read it was possible to use the new global method fetch in Nuxt 3 instead of #nuxtjs/axios (not up to date also), I thought it won't be too hard to code the authentication myself! But it stays a mystery to me and I only found documentation on Vue project (using Pinia to keep user logged in) and I'm a bit at a lost.
What I would like to achieve:
a login page with email and password, login request send to API (edit: done!)
get JWT token and user info from API (edit: done!) and store both (to keep user logged even if a page is refresh)
set the JWT token globally to header $fetch requests (?) so I don't have to add it to each request
don't allow access to other pages if user is not logged in
Then I reckon I'll have to tackle the refresh token subject, but one step at a time!
It will be really awesome to have some help on this, I'm not a beginner but neither a senior and authentication stuff still frightens me :D
Here is my login.vue page (I'll have to use Vuetify and vee-validate after that but again one step at a time!)
// pages/login.vue
<script setup lang="ts">
import { useAuthStore } from "~/store/auth";
const authStore = useAuthStore();
interface loginForm {
email: string;
password: string;
}
let loginForm: loginForm = {
email: "",
password: "",
};
function login() {
authStore.login(loginForm);
}
</script>
<template>
<v-container>
<form #submit.prevent="login">
<label>E-mail</label>
<input v-model="loginForm.email" required type="email" />
<label>Password</label>
<input v-model="loginForm.password" required type="password" />
<button type="submit">Login</button>
</form>
</v-container>
</template>
The store/auth.ts for now.
// store/auth.ts
import { defineStore } from 'pinia'
import { encodeURL } from '~~/services/utils/functions'
export const useAuthStore = defineStore({
id: 'auth,
state: () => ({
// TODO Initialize state from local storage to enable user to stay logged in
user: '',
token: '',
})
actions: {
async login(loginForm) {
const URL_ENCODED_FORM = encodeURL({
email: loginForm.email,
password: loginForm.password,
});
return await $fetch('api_route', {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
method: 'POST',
body: URL_ENCODED_FORM
}
}
}
})
i'm gonna share everything, even the parts you marked as done, for completeness sake.
Firstly, you will need something to generate a JWT in the backend, you can do that plainly without any packages, but i would recommend this package for that. Also i'll use objection.js for querying the database, should be easy to understand even if you don't know objection.js
Your login view needs to send a request for the login attempt like this
const token = await $fetch('/api/login', {
method: 'post',
body: {
username: this.username,
password: this.password,
},
});
in my case it requests login.post.ts in /server/api/
import jwt from 'jsonwebtoken';
import { User } from '../models';
export default defineEventHandler(async (event) => {
const body = await useBody(event);
const { id } = await User.query().findOne('username', body.username);
const token: string = await jwt.sign({ id }, 'mysecrettoken');
return token;
});
For the sake of simplicity i didn't query for a password here, this depends on how you generate a user password.
'mysecrettoken' is a token that your users should never get to know, because they could login as everybody else. of course this string can be any string you want, the longer the better.
now your user gets a token as the response, should just be a simple string. i'll write later on how to save this one for future requests.
To make authenticated requests with this token you will need to do requests like this:
$fetch('/api/getauthuser', {
method: 'post',
headers: {
authentication: myJsonWebToken,
},
});
i prefer to add a middleware for accessing the authenticated user in my api endpoints easier. this middleware is named setAuth.ts and is inside the server/middleware folder. it looks like this:
import jwt from 'jsonwebtoken';
export default defineEventHandler(async (event) => {
if (event.req.headers.authentication) {
event.context.auth = { id: await jwt.verify(event.req.headers.authentication, 'mysecrettoken').id };
}
});
What this does is verify that if an authentication header was passed, it checks if the token is valid (with the same secret token you signed the jwt with) and if it is valid, add the userId to the request context for easier endpoint access.
now, in my server/api/getauthuser.ts endpoint in can get the auth user like this
import { User } from '../models';
export default defineEventHandler(async (event) => {
return await User.query().findById(event.context.auth.id)
});
since users can't set the requests context, you can be sure your middleware set this auth.id
you have your basic authentication now.
The token we generated has unlimited lifetime, this might not be a good idea. if this token gets exposed to other people, they have your login indefinitely, explaining further would be out of the scope of this answer tho.
you can save your auth token in the localStorage to access it again on the next pageload. some people consider this a bad practice and prefer cookies to store this. i'll keep it simple and use the localStorage tho.
now for the part that users shouldnt access pages other than login: i set a global middleware in middleware/auth.global.ts (you can also do one that isnt global and specify it for specific pages)
auth.global.ts looks like this:
import { useAuthStore } from '../stores';
export default defineNuxtRouteMiddleware(async (to) => {
const authStore = useAuthStore();
if (to.name !== 'Login' && !localStorage.getItem('auth-token')) {
return navigateTo('/login');
} else if (to.name !== 'Login' && !authStore.user) {
authStore.setAuthUser(await $fetch('/api/getauthuser', {
headers: authHeader,
}));
}
});
I'm using pinia to store the auth user in my authStore, but only if the localstorage has an auth-token (jwt) in it. if it has one and it hasnt been fetched yet, fetch the auth user through the getauthuser endpoint. if it doesnt have an authtoken and the page is not the login page, redirect the user to it
With the help of #Nais_One I managed to do a manual authentication to a third-party API with Nuxt 3 app using client-side rendering (ssr: false, target: 'static' in nuxt.config.ts)
I still have to set the API URL somewhere else and to handle JWT token refresh but the authentication works, as well as getting data from a protected API route with the token in header and redirection when user is not logged.
Here are my finals files:
// pages/login.vue
<script setup lang="ts">
import { useAuthStore } from "~/store/auth";
const authStore = useAuthStore();
const router = useRouter();
interface loginForm {
email: string;
password: string;
}
let loginForm: loginForm = {
email: "",
password: "",
};
/**
* If success: redirect to home page
* Else display alert error
*/
function login() {
authStore
.login(loginForm)
.then((_response) => router.push("/"))
.catch((error) => console.log("API error", error));
}
</script>
<template>
<v-container>
<form #submit.prevent="login">
<label>E-mail</label>
<input v-model="loginForm.email" required type="email" />
<label>Password</label>
<input v-model="loginForm.password" required type="password" />
<button type="submit">Login</button>
</form>
</v-container>
</template>
For the auth store:
// store/auth.ts
import { defineStore } from 'pinia'
const baseUrl = 'API_URL'
export const useAuthStore = defineStore({
id: 'auth',
state: () => ({
/* Initialize state from local storage to enable user to stay logged in */
user: JSON.parse(localStorage.getItem('user')),
token: JSON.parse(localStorage.getItem('token')),
}),
actions: {
async login(loginForm) {
await $fetch(`${baseUrl}/login`, {
method: 'POST',
body: loginForm
})
.then(response => {
/* Update Pinia state */
this.user = response
this.token = this.user.jwt_token
/* Store user in local storage to keep them logged in between page refreshes */
localStorage.setItem('user', JSON.stringify(this.user))
localStorage.setItem('token', JSON.stringify(this.token))
})
.catch(error => { throw error })
},
logout() {
this.user = null
this.token = null
localStorage.removeItem('user')
localStorage.removeItem('token')
}
}
})
I also use the middleware/auth.global.ts proposed by Nais_One.
And this fetch-wrapper exemple I found here as well to avoid having to add token to every requests: https://jasonwatmore.com/post/2022/05/26/vue-3-pinia-jwt-authentication-tutorial-example and it seems to work perfectly. (I just didn't test yet the handleResponse() method).
Hope it can help others :)
That temporary alternative https://www.npmjs.com/package/#nuxtjs-alt/auth is up to date
And that https://www.npmjs.com/package/nuxtjs-custom-auth and https://www.npmjs.com/package/nuxtjs-custom-http work with Nuxt 3 $fetch and no need to use axios
Recently a new package was released that wraps NextAuth for Nuxt3. This means that it already supports many providers out of the box and may be a good alternative to look into.
You can install it via:
npm i -D #sidebase/nuxt-auth
Then it is pretty simple to add to your projects as you only need to include the module:
export default defineNuxtConfig({
modules: ['#sidebase/nuxt-auth'],
})
And configure at least one provider (like this example with Github):
import GithubProvider from 'next-auth/providers/github'
export default defineNuxtConfig({
modules: ['#sidebase/nuxt-auth'],
auth: {
nextAuth: {
options: {
providers: [GithubProvider({ clientId: 'enter-your-client-id-here', clientSecret: 'enter-your-client-secret-here' })]
}
}
}
})
Afterwards you can then get access to all the user data and signin/signup functions!
If you want to have a look at how this package can be used in a "real world" example, look at the demo repo in which it has been fully integrated:
https://github.com/sidebase/nuxt-auth-example
I hope this package may be of help to you and others!
Stumbling on the same issue for a personal project and what I do is declare a composable importing my authStore which is basically a wrapper over $fetch
Still a newb on Nuxt3 and Vue but it seems to work fine on development, still have to try and deploy it though
import { useAuthStore } from "../store/useAuthStore";
export const authFetch = (url: string, opts?: any | undefined | null) => {
const { jwt } = useAuthStore();
return $fetch(url, {
...(opts ? opts : {}),
headers: {
Authorization:`Bearer ${jwt}`,
},
});
};
And then I can just use it in my actions or components
// #/store/myStore.ts
export const useMyStore = defineStore('myStore', () => {
async getSomething() {
...
return authFetch('/api/something')
}
})
// #components/myComponent.vue
...
<script setup lang="ts">
const handleSomething = () => {
...
authFetch('/api/something')
}
</script>
Hope it helps someone !

Nuxt3 useFetch sends request only once

<script setup lang="ts">
const loadPost = () => {
console.log('load')
const { data, pending, error, refresh } = useFetch(
'https://jsonplaceholder.typicode.com/posts',
{
method: 'POST',
body: {
title: 'foo',
body: 'bar',
userId: 1,
},
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
})
console.log(data)
}
</script>
<template>
<div class="max-w-xs space-y-4 mx-auto mt-4">
<button #click.prevent="loadPost">Load post</button>
</div>
</template>
After clicking on the load button, I see every time the request is processed through the console log, but I do not see a new request in the network chrome devtools, I need to receive a response from the server every time, how can I do this?
Note: If I use a regular fetch(), then the request is sent every time, as it should be
my nuxt version - 3.0.0-rc.1
Thanks! Solved by adding param initialCache: false
useFetch('https://reqres.in/api/users?page=2?delay=1', { initialCache: false })
If you want to re-fetch the data, use the refresh() method returned from useFetch():
<script setup lang="ts">
const { data, pending, error, refresh } = await useFetch('https://reqres.in/api/users?page=2?delay=1')
const loadPost = () => {
refresh()
}
</script>
demo
Watch out! you need a key. In you don't provide one, will generate it from the URL. This might not get you the result you expect. Generate a different key if the data will change.
Here is in the docs
Options (from useAsyncData):
key: a unique key to ensure that data fetching can be properly de-duplicated across requests, if not provided, it will be generated based on the url and fetch options.

Use nuxt.js dynamic routing to pass parameters

I am going to query the database to return the result after receiving a parameter in the dynamic route, and find that the console reports an error.
TypeError: Cannot read property 'title' of null
When I went to see the request, I found that the first request returned the data, and then sent the same request but the spliced parameter was null and reported the error.
This is the second request 304
This is my page code.
`
<templat>
<div class="wrapper qa-content">
<div class="qa-title">
<div class="fl title">
<h2>{{problem.title}}</h2>
<p>
<span
>{{labes(index)}}</span>
<span>{{timeago(problem.createtime)}}</span>
</p>
</div>
import "~/assets/css/page-sj-qa-detail.css";
import axios from "axios";
import problemApi from "#/api/problem";
import replyApi from "#/api/reply";
import labelApi from "#/api/label";
export default {
asyncData({ params }) {
return axios
.all([
problemApi.findById(params.id),
replyApi.findByProId(params.id),
problemApi.findPL(params.id)
])
.then(
axios.spread(function(pojo, replyList, labelList) {
return {
problemId: params.id,
replyList: replyList.data.data,
problem: pojo.data.data,
labelList: labelList.data.data
};
})
);
},
data() {
return {
CurrentreplyId: "",
commentList: [],
labelName: [],
textarea: "",
dialogVisible: false,
content: "",
editorOption: {
// some quill options
modules: {
toolbar: [
[{ size: ["small", false, "large"] }],
["bold", "italic"],
[{ list: "ordered" }, { list: "bullet" }],
["link", "image"],
["blockquote", "code-block"]
]
}
}
};
},
mounted() {
console.log("app init, my quill insrance object is:", this.myQuillEditor);
},
methods: {
labes(index) {
console.log(this.labelList);
labelApi.findOne(this.labelList[index].labelid).then(res => {
this.labelName.push(res.data.data.labelname);
console.log(this.labelName);
});
},
check(id) {
console.log(id);
replyApi.findByParentid(id).then(res => {
this.commentList = res.data.data;
});
},
shows(item) {
console.log(item.id);
if (item.content === null || item.content === "" || item.content === "") {
return false;
} else {
return true;
}
}
`
This page is dynamically routed from the previous page.
<nuxt-link :to="'/qa/items/'+item.id" target="_blank">{{item.title}}</nuxt-link>
Ok. I think that helps. If I understand you correctly you do the following:
You are on a previous page
You click on the nuxt-link with the item id in the route
Your new route loads and you get an error: Because your page fetches the data from the API twice (but you did not reload the page) is that correct?
If so, I am not sure why your asyncData is executed twice, but you could probably solve it like this:
asyncData({ params }) {
if (params.id) {
return axios
.all([...
}
This would make sure that your request is only made ifan ID is present and it would not send null.
Honestly I don't know why the request is resent...
I also don't really understand how your API looks like. I see that it somehow returns promises, and that you can call methods on it... Something I haven't seen in such a context, but OK :).
Besides that you seem to execute further API calls in your methods.
Maybe that is the problem:
<span>{{labes(index)}}</span>
I don't see what would be passed into this method. This index is not defined...
When then calling the
labes() (did you mean labels?) method, you execute
labelApi.findOne(this.labelList[index].labelid)
but as index is undefined, I think this.labelList[index] will not return something useful and you make a request there?
(Depending on what your api.findOne() method does. Could it be that itself sends a request to an actual remote API?)
Cheers

Ensure variable exist in store state using vuex

I limit my application to load to the DOM only if I have user details:
<template>
<div id="app">
<template v-if="loggedUser">
//...
</template>
</div>
</template>
where loggedUser is a computed property from the store:
computed: {
loggedUser() {
return this.$store.getters.user;
}
}
The issue is that other components rely on this property existing. In one component, called admin under the route /admin for example, when mounted() I pass the user object from the store to a method which in turn executes an HTTP request:
mounted(){
this.someFunc(this.$store.getters.user)
}
but the issue is sometimes the user exists and sometimes the user doesn't. This is true if the user tries to load the app directly from the admin page andthe user doesn't exist. One possible option to solve this issue is to use watch over the a computed property that returns the user from the store:
computed: {
user() {
return this.$store.getters.user;
}
},
watch: {
user(newVal) {
if(newVal) this.someFunc(this.$store.getters.user)
}
}
and while this might work it feels tedious even for this example. Nevertheless, bigger more complex issues arise due to this problem.
Another possible option came is to try and save the user in localStorage but I guess vuex should be able to solve my issue without using any type of client side storage solutions. Any idea how I can solve this issue? Is there a more robust way to ensure that the user is available across my entire application?
If you using the vue router you can authenticate a user there:
const ifAuthenticated = (to, from, next) => {
if (store.state.token) { // or state.user etc.
next()
return
}
next('/Adminlogin') // if not authenticated
and the router path looks like that:
{
path: '/AdminUI',
name: 'AdminUI',
component: AdminUI,
beforeEnter: ifAuthenticated
}
Another possible solution:
<v-template
v-show="$store.state.isUserLoggedIn"
</v-template>
dont forget to import { mapState } from "vuex";
and in the store:
getters: {
isUserLoggedIn: state => !!state.token
}

Basic Vue store with with dependent API calls throughout the app

I tried asking this question in the Vue Forums with no response, so I am going to try repeating it here:
I have an app where clients login and can manage multiple accounts (websites). In the header of the app, there’s a dropdown where the user can select the active account, and this will affect all of the components in the app that display any account-specific information.
Because this account info is needed in components throughout the app, I tried to follow the store example shown here (Vuex seemed like overkill in my situation):
https://v2.vuejs.org/v2/guide/state-management.html
In my src/main.js, I define this:
Vue.prototype.$accountStore = {
accounts: [],
selectedAccountId: null,
selectedAccountDomain: null
}
And this is my component to load/change the accounts:
<template>
<div v-if="hasMoreThanOneAccount">
<select v-model="accountStore.selectedAccountId" v-on:change="updateSelectedAccountDomain">
<option v-for="account in accountStore.accounts" v-bind:value="account.id" :key="account.id">
{{ account.domain }}
</option>
</select>
</div>
</template>
<script>
export default {
name: 'AccountSelector',
data: function () {
return {
accountStore: this.$accountStore,
apiInstance: new this.$api.AccountsApi()
}
},
methods: {
updateSelectedAccountDomain: function () {
this.accountStore.selectedAccountDomain = this.findSelectedAccountDomain()
},
findSelectedAccountDomain: function () {
for (var i = 0; i < this.accountStore.accounts.length; i++) {
var account = this.accountStore.accounts[i]
if (account.id === this.accountStore.selectedAccountId) {
return account.domain
}
}
return 'invalid account id'
},
loadAccounts: function () {
this.apiInstance.getAccounts(this.callbackWrapper(this.accountsLoaded))
},
accountsLoaded: function (error, data, response) {
if (error) {
console.error(error)
} else {
this.accountStore.accounts = data
this.accountStore.selectedAccountId = this.accountStore.accounts[0].id
this.updateSelectedAccountDomain()
}
}
},
computed: {
hasMoreThanOneAccount: function () {
return this.accountStore.accounts.length > 1
}
},
mounted: function () {
this.loadAccounts()
}
}
</script>
<style scoped>
</style>
To me this doesn’t seem like the best way to do it, but I’m really not sure what the better way is. One problem is that after the callback, I set the accounts, then the selectedAccountId, then the selectedAccountDomain manually. I feel like selectedAccountId and selectedDomainId should be computed properties, but I’m not sure how to do this when the store is not a Vue component.
The other issue I have is that until the selectedAccountId is loaded for the first time, I can’t make any API calls in any other components because the API calls need to know the account ID. However, I’m not sure what the best way is to listen for this change and then make API calls, both the first time and when it is updated later.
At the moment, you seem to use store to simply hold values. But the real power of the Flux/Store pattern is actually realized when you centralize logic within the store as well. If you sprinkle store-related logic across components throughout the app, eventually it will become harder and harder to maintain because such logic cannot be reused and you have to traverse the component tree to reach the logic when fixing bugs.
If I were you, I will create a store by
Defining 'primary data', then
Defining 'derived data' that can be derived from primary data, and lastly,
Defining 'methods' you can use to interact with such data.
IMO, the 'primary data' are user, accounts, and selectedAccount. And the 'derived data' are isLoggedIn, isSelectedAccountAvailable, and hasMoreThanOneAccount. As a Vue component, you can define it like this:
import Vue from "vue";
export default new Vue({
data() {
return {
user: null,
accounts: [],
selectedAccount: null
};
},
computed: {
isLoggedIn() {
return this.user !== null;
},
isSelectedAccountAvailable() {
return this.selectedAccount !== null;
},
hasMoreThanOneAccount() {
return this.accounts.length > 0;
}
},
methods: {
login(username, password) {
console.log("performing login");
if (username === "johnsmith" && password === "password") {
console.log("committing user object to store and load associated accounts");
this.user = {
name: "John Smith",
username: "johnsmith",
email: "john.smith#somewhere.com"
};
this.loadAccounts(username);
}
},
loadAccounts(username) {
console.log("load associated accounts from backend");
if (username === "johnsmith") {
// in real code, you can perform check the size of array here
// if it's the size of one, you can set selectedAccount here
// this.selectedAccount = array[0];
console.log("committing accounts to store");
this.accounts = [
{
id: "001234",
domain: "domain001234"
},
{
id: "001235",
domain: "domain001235"
}
];
}
},
setSelectedAccount(account) {
this.selectedAccount = account;
}
}
});
Then, you can easily import this store in any Vue component, and start referencing values, or call methods, from this store.
For example, suppose you are creating a Login.vue component, and that component should redirect when user object becomes available within a store, you can achieve this by doing the following:
<template>
<div>
<input type="text" v-model="username"><br/>
<input type="password" v-model="password"><br/>
<button #click="submit">Log-in</button>
</div>
</template>
<script>
import store from '../basic-store';
export default {
data() {
return {
username: 'johnsmith',
password: 'password'
};
},
computed: {
isLoggedIn() {
return store.isLoggedIn;
},
},
watch: {
isLoggedIn(newVal) {
if (newVal) { // if computed value from store evaluates to 'true'
console.log("moving on to Home after successful login.");
this.$router.push({ name: "home" });
}
}
},
methods: {
submit() {
store.login(this.username, this.password);
}
}
};
</script>
In addition, with isSelectedAccountAvailable we compute, we can easily disable/enable button on the screen, to prevent user from making API calls until an account is selected:
<button :disabled="!isSelectedAccountAvailable" #click="performAction()">make api call</button>
If you want to see the whole project, you can access it from this runnable codesandbox. Pay attention at how basic-store.js is defined and used in Login.vue and Home.vue. And, if you'd like, you can also see how store is defined in vuex by taking a peek at store.js.
Good luck!
Updated:
About how you should organize dependent/related API calls, the answer is actually right in front of you. If you take a closer look at the store, you'll notice that my login() method subsequently calls this.loadAccounts(username) once the login succeeds. So, basically, you have all the flexibility to chain/nested API calls in store's methods to accommodate your business rules. The watch() is there simply because the UI needs to perform navigation based on change(s) made in the store. For most simple data changes, computed properties will suffice.
Further, from how I designed it, the reason watch() is used in <Login> component is twofold:
Separation of concerns: for me who has been working on server-side code for years, I'd like my view-related code to be cleanly separated from model. By restricting navigation logic inside a component, my model in a store doesn't need to know/care about navigation at all.
However, even if I don't separate concerns, it will still be pretty hard to import vue-router into my store. This is because my router.js already imports basic-store.js to perform navigation guard preventing unauthenticated users from accessing <Home> component:
router.beforeEach((to, from, next) => {
if (!store.isLoggedIn && to.name !== "login") {
console.log(`redirect to 'login' instead of '${to.name}'.`);
next({ name: "login" });
} else {
console.log(`proceed to '${to.name}' normally.`);
next();
}
});
And, because javascript doesn't support cyclic dependency yet (e.g., router imports store, and store imports router), to keep my code acyclic, my store can't perform route navigations.