Understanding context and app methods in NUXT - vue.js

I am trying to use bugsnagClient and its notify method in plugins/axios.js I have this code in plugins/bugsnag.js
import Vue from "vue"
import bugsnag from "#bugsnag/js"
import bugsnagVue from "#bugsnag/plugin-vue"
// const bugsnagClient = bugsnag(`${process.env.BUGSNAG_API_KEY}`)
var bugsnagClient = bugsnag({
apiKey: "",
notifyReleaseStages: ["production"]
})
bugsnagClient.use(bugsnagVue, Vue)
I want to attach a method to app or context as
export default ({ app }, inject) => {
function bugsnagNotify(error) {
return bugsnagClient.notify(new Error(error))
}
// Set the function directly on the context.app object
app.bugsnagNotify = bugsnagNotify
}
And I want to use it in plugins/axios.js
export default function({ store, app }) {
if (store.getters.token) {
console.log(app.bugsnagNotify("ss"))
app.$axios.setToken(store.getters.token, "Bearer")
} else {
//app.$bugsnag.notify(new Error("Bearer tooken is missing in Axios request."))
}
}
In this file, when I do console.log for just app
I can see bugsnagNotify: ƒ bugsnagNotify(error)
but when I call app.bugsnagNotify("error") I only get error such as VM73165:37 TypeError: app.bugsnagNotify is not a function
I have also tried this in plugins/bugsnag.js
export default (ctx, inject) => {
inject('bugsnag', bugsnagClient)
}
I only get an error as
app.$bugsnag.notify(new Error("Bearer tooken is missing in Axios request."))

If you are injecting into context inside one plugin and want to use that function inside another, you need to make sure that the plugin in which you are injecting comes first inside nuxt.config.js
...
plugins: [
'~/plugins/bugsnag.js',
'~/plugins/axios.js'
],
...

Related

Access Nuxt custom plugin from Composition API

I am using VueClipboard in my nuxt project.
https://www.npmjs.com/package/vue-clipboard2
I have a plugin file vue-clipboard.js
import Vue from "vue";
import VueClipboard from 'vue-clipboard2';
Vue.use(VueClipboard);
It is imported into nuxt.config
plugins: ['#/plugins/vue-clipboard'],
This sets up a global variable $copyText and in nuxt without the composition API I can do something like
methods: {
async onCopyCodeToClipboard() {
const code = 'code'
await this.$copyText(code)
},
},
However inside the setup using the composition API (#nuxtjs/composition-api) when I write a function I do not have access to this.$copyText
const onCopyCodeToClipboard = async () => {
const code = context.slots.default()[0].elm.outerHTML
// -> Can't use this here - await this.$copyText(code)
}
So how do I make $copyText available to use inside the composition API?
I was able to get this to work via the Nuxt useContext() method:
import { useContext } from '#nuxtjs/composition-api'
export default function () {
const { $copyText } = useContext();
$copyText('code');
}

How to create a helper function in NuxtJs to use inside vuex and components?

I want to create a plugin for Nuxtjs to log everything I want only in client mode, something like this :
// ~/plugins/client-log.js
export default ({ app }, inject) => {
app.clog = string => console.log(string)
}
This plugin is working in components where I have access to context for example:
export default {
fetch({app}){
app.clog("some string")
}
};
But I want to be able to use it inside vuex (actions, mutations...). How can I do that?
Thanks in advance.
You're so close, you just need to change one thing:
// ~/plugins/client-log.js
export default ({ app }, inject) => {
inject('clog', string => console.log(string))
}
Then you're able to call it like:
export default {
fetch({app}){
// Note: inject will automatically prefix with a "$"
app.$clog("some string")
},
mounted() {
// this.$clog can also be accessed within vuex
this.$clog("I'm in a component")
}
};

Not able to access i18 plugin from mutation in classic mode store in Nuxt application

I'm trying to implement Vuex i18n package within my Nuxt application. In my nuxt.conf.js file in plugins array I have:
{
src: '#/plugins/i18n.js',
ssr: false
},
plugins/i18n.js file is:
import Vue from "vue";
import vuexI18n from "vuex-i18n/dist/vuex-i18n.umd.js";
import toEnglish from "../translations/toEnglish";
import toSpanish from "./../translations/toSpanish";
import toGerman from "./../translations/toGerman";
export default ({ store }) => {
Vue.use(
vuexI18n.plugin,
store,
{
onTranslationNotFound: function (locale, key) {
console.warn(`vuex-i18n :: Key '${key}' not found for locale '${locale}'`)
}
}
);
// register the locales
Vue.i18n.add('en', toEnglish);
Vue.i18n.add('de', toGerman);
Vue.i18n.add('es', toSpanish);
// Set the start locale to use
Vue.i18n.set('de');
Vue.i18n.fallback('en');
}
Last thing is my store. I'm using classic mode of vuex store in Nuxt:
import Vuex from "vuex";
const store = () => {
return new Vuex.Store({
state: () => ({
currentLanguage: ''
}),
mutations: {
changeLang(state, response) {
if (response) {
console.log(this);
state.currentLanguage = response;
this.i18n.set(response);
}
}
}
})
};
export default store;
As you can see in store file in mutation I'm trying to access i18n plugin with this keyword. Unfortunetally in print error in console:
TypeError: Cannot read property 'set' of undefined
this which I consoled also inside mutation is:
I changed this.i18n.set(response); to state.i18n.locale = response; inside my mutation and now it seems working.
For some reason when I call this mutation my video.js player refresh. I will try to find out why.

How to attach axios / axios interceptor to Nuxt globally ?

how would i go about attaching axios / axios interceptor globally to nuxt (so its available everywhere), same how i18n is attached ?
The idea is that i would like to have a global axios interceptor that every single request goes through that interceptor.
Thanks
you can create a plugin called axios (/plugins/axios.js)
import Vue from 'vue';
import axios from 'axios';
axios.interceptors.request.use((config) => {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
Vue.use(axios);
then define this in nuxt.config.js
module.exports = {
//....
plugins: [
'~/plugins/axios',
],
//....
};
thats all, your interceptor is now working globally
It's hidden in the documentation - https://nuxtjs.org/docs/2.x/directory-structure/plugins
See number 3 of the first photo:
// plugins/axios.js
export default function ({ $axios, redirect }) {
$axios.onError(error => {
if (error.response.status == 404) {
redirect('/sorry')
}
})
}
then define this in nuxt.config.js
module.exports = {
//....
plugins: [
'~/plugins/axios',
],
//....
};
Maybe will be helpful for someone.
It just sets the lang parameter for every request.
Сreate a plugin called axios (/plugins/axios.js). Put it there:
export default function ({ $axios, app, redirect }) {
$axios.onRequest(config => {
config.params = config.params || {}; // get existing parameters
config.params['lang'] = app.i18n.locale;
})
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 400) {
redirect('/400')
}
})
}
Add in nuxt.config.js:
module.exports = {
plugins: [
'~/plugins/axios'
]
};
Create a new module, call it request.js for example.
import axios from 'axios'
const instance = axios.create({
baseURL: 'http://example.org' // if you have one
})
// Put all interceptors on this instance
instance.interceptors.response.use(r => r)
export default instance
Then simply import that instance whenever you need it and use it like it was a normal axios instance:
import request from './request'
await request.get('/endpoint')
// or use promises
request.get('/endpoint').then(data => data)
If you really need it globally you can use the following code in your entry point of the application:
import request from './request'
global.request = request
// use it:
await request.get('example.org')
Or you can add it to the vue protype
Vue.prototype.$request = request
// in your component:
this.$request.get()
I'd advice against it though.

Axios interceptor in vue 2 JS using vuex

I store token after success login call in vuex store like this:
axios.post('/api/auth/doLogin.php', params, axiosConfig)
.then(res => {
console.log(res.data); // token
this.$store.commit('login', res.data);
})
axiosConfig is file where I only set baseURL export default { baseURL: 'http://localhost/obiezaca/v2' } and params is just data sent to backend.
My vuex file looks is:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
logged: false,
token: ''
},
mutations: {
login: (state, response) => {
state.logged = true;
state.token = response;
console.log('state updated');
console.log('state.logged flag is: '+state.logged);
console.log('state.token: '+state.token);
},
logout: (state) => {
state.logged = false;
state.token = '';
}
}
});
It is working correctly, I can re-render some of content in my SPA basing on v-if="this.$store.state.logged" for logged user. I'm able to access this.$store.state.logged from any component in my entire app.
Now I want to add my token to every request which call my rest API backend. I've created basic axios http interceptor which looks like this:
import axios from 'axios';
axios.interceptors.request.use(function(config) {
const token = this.$store.state.token;
if(token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, function(err) {
return Promise.reject(err);
});
Now I have 2 problems/questions about it.
I know that it is available to use this.$store.state.logged or this.$store.state.token across every component but can I use it same way in single javascript file?
Where should I execute/start my interceptor javascript file? It is independent file which lays in my app main folder but I am not calling it anywhere, in angularJS which I was working before, I had to add $httpProvider.interceptors.push('authInterceptorService'); in config but I don't know how to do same thing in vue architecture. So where should I inject my interceptor?
EDIT
I followed GMaiolo tips I added
import interceptor from './helpers/httpInterceptor.js';
interceptor();
to my main.js file and I refactor my interceptor to this:
import axios from 'axios';
import store from '../store/store';
export default function execute() {
axios.interceptors.request.use(function(config) {
const token = this.$store.state.token;
if(token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, function(err) {
return Promise.reject(err);
});
}
Result of this changes is that every already existing backend calls ( GET ) which don't need token to work stopped working but it is logical because I didn't clarified to which request it should add token so it is trying to add it everywhere and in my interceptor something is still wrong and that is why every already exisitng request stopped working.
When I try to do backend POST call in browser console I still get this error:
TypeError: Cannot read property '$store' of undefined
Although I import store to my interceptor file. Any ideas? I can provide some more information if any needed.
I additionally add screenshot of this main, store and interceptor tree structure so you can see that I'm importing fron correct path:
1.
First of all I'd use a Vuex Module as this Login/Session behavior seems to be ideal for a Session module. After that (which is totally optional) you can set up a Getter to avoid accessing the state itself from outside Vuex, you'd would end up with something like this:
state: {
// bear in mind i'm not using a module here for the sake of simplicity
session: {
logged: false,
token: ''
}
},
getters: {
// could use only this getter and use it for both token and logged
session: state => state.session,
// or could have both getters separated
logged: state => state.session.logged,
token: state => state.session.token
},
mutations: {
...
}
With those getters set, you can get the values a bit easier from components. With either using this.$store.getters.logged (or the one you'd want to use) or using the mapGetters helper from Vuex [for more info about this you can check the getters docs]:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
...mapGetters([
'logged',
'token'
])
}
}
2.
I like to run Axios' interceptors along with Vue instantation in main.js creating, importing and executing an interceptors.js helper. I'd leave an example so you get an idea, but, then again, this is my own preference:
main.js
import Vue from 'vue';
import store from 'Src/store';
import router from 'Src/router';
import App from 'Src/App';
// importing the helper
import interceptorsSetup from 'Src/helpers/interceptors'
// and running it somewhere here
interceptorsSetup()
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
});
interceptors.js
import axios from 'axios';
import store from 'your/store/path/store'
export default function setup() {
axios.interceptors.request.use(function(config) {
const token = store.getters.token;
if(token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, function(err) {
return Promise.reject(err);
});
}
And there you'd end up having all the behavior cleanly encapsulated.
I did the same logic. however, I just change the file name. I used axios/index.js but the store is undefined there. so I just change the file name axios/interceptor.js and Don't know store data is accessible look at my below image