I have a file store/service.js and I want use router.
I make this:
import { useRoute } from 'vue-router';
const router = useRoute();
function exceptionHandler(error) {
if (error.response.status === 401) {
router.push('/user/login');
} else if (error.response.status === 404) {
throw new Error(error.response.data.Message || error.message);
} else {
router.push('/error');
}
}
but received a error undefined in router use.
note: this is not inside setup tag, this is a js external file
useRoute() can only be used inside setup so try to write your function as a composable like this
import { useRoute } from 'vue-router';
export function useExceptionHandler(){
const router = useRoute();
function exceptionHandler(error) {
if (error.response.status === 401) {
router.push('/user/login');
} else if (error.response.status === 404) {
throw new Error(error.response.data.Message || error.message);
} else {
router.push('/error');
}
}
return {
exceptionHandler,
}
}
and you can use it like this on your component
<script setup>
const { exceptionHandler } = useExceptionHandler();
</script>
Yeah, this is not quite possible, you can only use useRoute in setup, because that's a hook. What you can do, however, is to import the router from its file into your script and use it.
Related
I want to format my date-time outside the component.
function transformDatetime(config, value) {
if (value) {
return $d(new Date(value), config);
}
return $t('-');
}
I'm trying to get $t from the App instance. But it only works in the context of the component, just like useI18n.
import { getCurrentInstance } from 'vue'
export default function useGlobal() {
const instance = getCurrentInstance()
if (!instance) return
return instance.appContext.config.globalProperties
}
I found a solution. Just import your i18n into a file outside the application and use i18n.global.t
import { createI18n } from "vue-i18n"
export const i18n = createI18n({
legacy: false,
locale: 'ja',
fallbackLocale: 'en',
messages,
})
import { i18n } from '#/i18n'
const { t: $t, d: $d, n: $n } = i18n.global
const expample = $t('some-text')
I think your method should also be a composable so you can simply use the method useI18n() inside.
Something like
use-transform-datetime.ts
import { useI18n } from 'vue-i18n'
export const useTransformDatetime = () => {
const { t } = useI18n()
const transformDatetime = (config, value) => {
if (value) {
return $d(new Date(value), config)
}
return t('-')
}
return {
transformDatetime
}
}
then simply use it in your vue file who need it
<script setup>
import { useTransformDatetime } from 'path-to-your-composables/use-transform-datetime'
const { transformDatetime } = useTransformDatetime()
console.log(transformDatetime(config, 'value'))
</script>
This way you can even add your others transform methods in the same composable
Try to use of the useI18n composable function from vue-i18n to get the t method instead of $t:
<script setup>
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
console.log(t('greeting'))
</script>
I'm learning Vue SSR. I tried to rewrite one of my project and use Server-Side Rendering that is suggested by Vite documentation. In that page there is a link to this repo as a starter template for Vue 3 SSR. I used that and now I want to use Vuex in my project. I want to have a separate file (for example store.js) similar to router.js that is responsible for my vuex. Unfortunately in that repo there is not such a file. So I searched in web and found this guide and this repo that are similar but not exact match to my first repository. Because I'm new in this topic and the syntax and maybe plugins that were used in that two repo are different, I'm a bit confused that How to use both Vuex and Vue-router correctly? in my SSR app.
What I understood is that if we accept the first repo as a base to add Vuex, we need to change at least 4 files: main.js, entry-server.js, entry-client.js and added store.js.
Below is that four file in my project:
main.js:
import { createSSRApp } from 'vue'
import App from './App.vue'
import { createRouter } from './router'
import { createStore } from './store'
// SSR requires a fresh app instance per request, therefore we export a function
// that creates a fresh app instance. If using Vuex, we'd also be creating a
// fresh store here.
export function createApp() {
const app = createSSRApp(App)
const router = createRouter()
const store = createStore()
app.use(router)
app.use(store)
return { app, router, store }
}
entry-client.js:
import { createApp } from './main'
const { app, router } = createApp();
// wait until router is ready before mounting to ensure hydration match
router.isReady().then(() => {
app.mount('#app')
})
entry-server.js:
import { basename } from 'node:path'
import { renderToString } from 'vue/server-renderer'
import { createApp } from './main'
export async function render(url, manifest) {
const { app, router } = createApp()
// set the router to the desired URL before rendering
await router.push(url)
await router.isReady()
// passing SSR context object which will be available via useSSRContext()
// #vitejs/plugin-vue injects code into a component's setup() that registers
// itself on ctx.modules. After the render, ctx.modules would contain all the
// components that have been instantiated during this render call.
const ctx = {}
const html = await renderToString(app, ctx)
// the SSR manifest generated by Vite contains module -> chunk/asset mapping
// which we can then use to determine what files need to be preloaded for this
// request.
const preloadLinks = renderPreloadLinks(ctx.modules, manifest)
return [html, preloadLinks]
}
function renderPreloadLinks(modules, manifest) {
let links = ''
const seen = new Set()
modules.forEach((id) => {
const files = manifest[id]
if (files) {
files.forEach((file) => {
if (!seen.has(file)) {
seen.add(file)
const filename = basename(file)
if (manifest[filename]) {
for (const depFile of manifest[filename]) {
links += renderPreloadLink(depFile)
seen.add(depFile)
}
}
links += renderPreloadLink(file)
}
})
}
})
return links
}
function renderPreloadLink(file) {
if (file.endsWith('.js')) {
return `<link rel="modulepreload" crossorigin href="${file}">`
} else if (file.endsWith('.css')) {
return `<link rel="stylesheet" href="${file}">`
} else if (file.endsWith('.woff')) {
return ` <link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`
} else if (file.endsWith('.woff2')) {
return ` <link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`
} else if (file.endsWith('.gif')) {
return ` <link rel="preload" href="${file}" as="image" type="image/gif">`
} else if (file.endsWith('.jpg') || file.endsWith('.jpeg')) {
return ` <link rel="preload" href="${file}" as="image" type="image/jpeg">`
} else if (file.endsWith('.png')) {
return ` <link rel="preload" href="${file}" as="image" type="image/png">`
} else {
// TODO
return ''
}
}
store.js:
import Vuex from 'vuex'
export { createStore }
function createStore() {
const store = Vuex.createStore({
state() {
return {
todoList: []
}
},
actions: {
fetchTodoList({ commit }) {
const todoList = [
{
id: 0,
text: 'Buy milk'
},
{
id: 1,
text: 'Buy chocolate'
}
]
return commit('setTodoList', todoList)
}
},
mutations: {
setTodoList(state, todoList) {
state.todoList = todoList
}
}
})
return store
}
They are some combinations of that two repo codes. But probably needs to have some changes to work perfectly. I'm not sure that if any other files is necessary to add or change. I’m so grateful for the help of a developer that have more experience in this topic to correct my files to use both Vuex and Vue-router.
I am currently working on upgrading a project to Vue3. Inside the project we have several files that create global variables inside a boot directory:
src/boot/auth.js
src/boot/axios.js
src/boot/cranky.js
....
Each of these files creates global variables that I am using throughout the project. For example my auth.js file:
import auth from '../app/auth';
import { createApp } from 'vue';
export default async ({
router,
Vue
}) => {
const app = createApp({});
app.config.globalProperties.$auth = auth;
// Vue.prototype.$auth = auth;
//This works with logic in MainLayout to permit users to see particular screens
//after they are logged in.
router.beforeResolve((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// eslint-disable-next-line no-unused-vars
let user;
if (app.config.globalProperties.$auth.isAuthenticated()) {
// TODO: record screens viewed by router url
next();
} else {
next({
path: '/home'
});
}
}
next();
});
};
After research and reading I understand that global variables are created like this:
app.config.globalProperties.$auth = auth;, Which then should be called from other component files using this.$auth. In my case, however, this returns as undefined.
My theory is that there is an issue with my createapp. Am I calling that correctly, or am I missing something else?
Thanks.
EDIT
added code requested by #tony19
The following the full script tag. My understanding and I probably am wrong, is that the this works as a global in vue3.
<script>
import HeaderScoreIndicator from '../components/HeaderScoreIndicator.vue';
import ExitResearchControl from '../components/ExitResearchControl.vue';
import {mq} from 'src/boot/mq'
export default {
components: {
HeaderScoreIndicator,
ExitResearchControl
},
data: function () {
return {
tab: 'denial',
signedIn: false,
username: null,
user: null
};
},
computed: {
/*
These 4 functions bind the colors to the background elements
*/
title: function () {
return this.$store.state.globals.title;
},
toolbarStyle: function () {
return 'padding-top: 10px; background-color: ' + this.$store.state.globals.toolbarColor;
},
footerStyle: function () {
return `background-color: ${this.$store.state.globals.toolbarColor};`;
},
backgroundStyle: function () {
if(!this.$mq.phone){
return `background: linear-gradient(180deg, ${this.$store.state.globals.backgroundGradientTop} 0%, ${this.$store.state.globals.backgroundGradientBottom} 100%);
display: flex;
justify-content: center;`;
} else{
return `background: linear-gradient(180deg, ${this.$store.state.globals.backgroundGradientTop} 0%, ${this.$store.state.globals.backgroundGradientBottom} 100%);`;
};
},
limitSize: function(){
if(!this.$mq.phone){
return 'max-width: 1023px; width: 100%;'
} else{
return
};
}
},
/*
In the beforeCreate function, we're setting up an event listener to detect
when we've logged in. At the successful login we can push the user to
the correct screen.
*/
beforeCreate: function () {
console.log(this.$auth);
return this.$auth.getState().then(data => {
if (this.$auth.isAuthenticated()) {
this.username = this.$auth.getEmail();
return this.initialize(data).then(() => this.signedIn = true);
} else {
this.signedIn = false;
return newUserLanguageSelection()
}
}).catch(e => {
this.signedIn = false;
console.error(e);
});
},
methods: {
newUserLanguageSelection: function(){
if(localStorage.getItem('languageSet') != 'true'){
return this.$router.push('/language');
}
},
initialize: function (data) {
this.$store.commit('globals/dataLoaded');
this.$store.commit('scoring/setUsername', this.username);
return this.$store.dispatch('scoring/initializeScoring', { points: data.score | 0 })
.then(() => {
if(this.$store.state.globals.testQuiz){
return;
} else if(localStorage.getItem('languageSet') != 'true'){
return this.$router.push('/language');
}else if (data.seenAnalyticsDialog == true) {
return this.$router.push('/home');
} else {
return this.$router.push('/consent');
}
})
.catch(e => {
//silence NavigationDuplicated errors
if (e.name != "NavigationDuplicated")
throw e;
});
}
}
};
</script>
In the previous Vue 2 project, it looks like Vue was passed to the auth.js module so that it could attach $auth to the Vue prototype.
However, in an attempt to upgrade that particular code in the auth.js module to Vue 3, you ignore the Vue argument, and create a throwaway application instance to create the $auth global property:
// src/boot/auth.js
export default async ({
router,
Vue,
}) => {
const app = createApp({}); ❌ local app instance
app.config.globalProperties.$auth = auth;
⋮
}
But that won't work because the app instance is not somehow hooked into the application instance you'll eventually mount in main.js.
Fixing the upgrade
Your main.js probably looks similar to this:
// src/main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import auth from './boot/auth';
auth({ router, Vue });
⋮
Vue.createApp(App).mount('#app');
To correctly upgrade the original code, you should pass the app instance from main.js to auth.js:
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import auth from './boot/auth';
const app = createApp(App);
auth({ router, app });
⋮
app.mount('#app');
And use that app in the auth.js module:
// src/boot/auth.js
export default async ({
router,
app, 👈
}) => {
👇
app.config.globalProperties.$auth = auth;
⋮
}
Convert to Vue plugin
Assuming you don't need to await the return of the auth.js module, you could make that module a Vue plugin instead, which automatically receives the app instance as the first argument. The second argument receives the options passed in from app.use():
// src/boot/auth.js
export default {
install(app, { router }) {
app.config.globalProperties.$auth = auth;
router.beforeResolve(⋯);
}
}
// src/main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import auth from './boot/auth';
const app = createApp(App);
app.use(auth, { router });
⋮
app.mount('#app');
I've been following this guide to making a global loader component but there's an issue with setting up the interceptors with the config.
Error: TypeError: Cannot read properties of undefined (reading 'config')
I'm using vue 3.2.2, vuex 4.0.2, axios 0.21, and Laravel 8
The loader component is not working and I suspect it might be due to the config error.
app.js
<script>
import { createApp } from 'vue'
import { createStore } from 'vuex'
import router from './router';
import App from './components/App';
import { loader } from "./loader";
const store = createStore({
state() {
},
modules: {
loader,
}
})
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
</script>
App.vue
<script>
import Sidebar from "../components/Sidebar";
import {mapState} from "vuex/dist/vuex.mjs";
import Loader from "./Loader";
import axios from "axios";
axios.defaults.showLoader = true;
export default {
components: {
Sidebar,
Loader
},
computed: {
...mapState('loader', ['loading'])
},
created() {
axios.interceptors.request.use(
config => {
if (config.showLoader) {
store.dispatch('loader/pending');
}
return config;
},
error => {
if (error.config.showLoader) {
store.dispatch('loader/done');
}
return Promise.reject(error);
}
);
axios.interceptors.response.use(
response => {
if (response.config.showLoader) {
store.dispatch('loader/done');
}
return response;
},
error => {
let response = error.response;
if (response.config.showLoader) {
store.dispatch('loader/done');
}
return Promise.reject(error);
}
)
}
}
</script>
Your interceptor arrow function having issues with config param
axios.interceptors.request.use((config) => {
if (config.showLoader) {
store.dispatch('loader/pending');
}
return config;
}, (error) => {
if (error.config.showLoader) {
store.dispatch('loader/done');
}
return Promise.reject(error);
});
axios.interceptors.response.use((response) => {
if (response.config.showLoader) {
store.dispatch('loader/done');
}
return response;
}, (error) => {
let response = error.response;
if (response.config.showLoader) {
store.dispatch('loader/done');
}
return Promise.reject(error);
})
In my Vue/Nuxt application, I have a plugin with the following code:
import Vue from 'vue';
import axios from 'axios';
axios.interceptors.response.use(res => {
console.log('Interceptor response: ', res.data);
return res;
},
err => {
if (err.response.status === 401) {
this.$store.dispatch(AUTH_LOGOUT);
redirect('/');
}
});
Vue.use(axios);
The problem is that both 'redirect' and 'this.$store' appear to be undefined. Can you help me with how I can use redirect or store inside this vue/nuxt plugin?
You can use context variable.
Example:
plugins/axios.js
import axios from 'axios'
export default function (context, inject) {
axios.interceptors.response.use(res => {
console.log('Interceptor response: ', res.data);
return res;
}, err => {
if (err.response.status === 401) {
context.redirect('/');
context.store.dispatch(AUTH_LOGOUT);
}
});
inject('axios', axios)
}
Inject in $root & context