I'm trying to create a Vue mixin which would handle my Axios calls (I need to do a lot of similar calls from different components).
Mixin code is like this:
import axios from "axios";
const restService = {
methods: {
executeRequest( url, payload ) {
axios
.post(url, JSON.stringify(payload), {
headers: { 'Content-Type': 'application/json' }
})
.then(response => {
return response.data;
})
}
}
}
export default restService
Which I then call from my component like this:
import restService from "#/services/RestService";
export default {
//...
mixins: [restService],
methods: {
saveChanges() {
//...
let response = this.executeAuthorizedRequest( changeData );
this.$notify({
//some logic that depends on my response object
});
}
}
}
The problem is that .then() from the mixin is for some reason called later than my this.notify() from component which leads to an error.
Is there a way to make sure that .then() is called before proceeding? The same axios code works as expected when written in component method itself.
I'm relatively new to JS frameworks so maybe I don't understand something obvious.
Axios calls are asynchronous. So the response may not be returned before this.notify(). You may consider rewriting the axios calls with async/await or Promise to make sure the response is returned. Here is an example with async/await:
import axios from "axios";
const restService = {
methods: {
executeRequest(url, payload) {
return axios
.post(url, JSON.stringify(payload), {
headers: {
'Content-Type': 'application/json'
}
})
}
}
}
export default restService
And use the call like:
import restService from "#/services/RestService";
export default {
//...
mixins: [restService],
methods: {
async saveChanges() {
//...
let response = await this.executeAuthorizedRequest( changeData );
this.$notify({
// do anything here with response.data
});
}
}
}
Try out to use async/await when you call the method and inside the method just return the request :
import axios from "axios";
const restService = {
methods: {
executeRequest( url, payload ) {
return axios.post(url, JSON.stringify(payload), {
headers: { 'Content-Type': 'application/json' }
})
}
}
}
export default restService
then call it like :
import restService from "#/services/RestService";
export default {
//...
mixins: [restService],
methods: {
async saveChanges() {
//...
let response = await this.executeAuthorizedRequest( changeData );
this.$notify({
//some logic that depends on my response object
});
}
}
}
Related
I am using Vuejs with Vuex and I have actions that are used to fetch data from the database, I want the fetch to be before rendering the components depended on this data. So where can I put these actions in my code ?
Mounted / BeforeMounted
Created
On Vuejs 3 you have onMounted
https://vuejs.org/api/
You can use your action (ex: getData) on onMounted hook
and you can use async await to be sure that your action call is done before moving to the next step
here is an exemple (i used axios just for the exemple you are free to use any methode to get your data) :
<template>
<h1>My component</h1>
</template>
<script>
import axios from 'axios'
export default {
name: 'MyComponent',
setup() {
const baseUrl = 'your-api-base-url'
const apiClient = axios.create({
baseURL: baseUrl,
withCredentials: false,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
const getData = async() => {
try {
return await apiClient.get('/api-path', {}) // here i call my
api to get data you can call your function
} catch (error) {
throw new Error(error)
}
}
onMounted(() => {
getData()
})
}
}
</script>
How to correctly transfer or rewrite the function in actions that will receive the first photo from each album in?
my photos models:
import { api } from "#/apis/config";
const endPoint = 'photos'
const state = {
photos: []
}
const getters = {}
const mutations = {
SET_PHOTOS(state, data) {
state.photos = data
}
}
const actions = {
loadMore(id) {
api.get(`photos?albumId=${id}`).then(response => {
return response.data[0].thumbnailUrl;
});
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
};
And my album component:
...
<script>
import {mapActions} from "vuex";
export default {
name: "GridAlbums",
props: ['album'],
data() {
return {
loading: true,
errored: false,
photo: null
}
},
mounted() {
this.photo = this.loadMore(this.album.id)
},
methods: {
...mapActions("photos", ["loadMore"])
}
}
</script>
...
Here is my configuration file for api, where VUE_APP_HOST is https://jsonplaceholder.typicode.com/
import axios from 'axios'
export const api = axios.create({
baseURL: process.env.VUE_APP_HOST,
})
api.interceptors.response.use(
response => {
return response
},
error => {
return Promise.reject(error)
}
)
If you get the data this way, then everything turns out as it should:
Try adding async / await in vuex actions, because actions must be asynchronous and store and mutations synchronous.
Also use try / catch instead Promise that way you keep your code cleaner
I'm trying to do a simple get request to my backend server with axios. I'm getting this stacktrace in the console:
TypeError: Cannot read property 'protocol' of undefined
at isURLSameOrigin (isURLSameOrigin.js?3934:57)
at dispatchXhrRequest (xhr.js?b50d:109)
at new Promise (<anonymous>)
at xhrAdapter (xhr.js?b50d:12)
at dispatchRequest (dispatchRequest.js?5270:52)
Here are the code files: https://gist.github.com/DavidMarcu/68f6bd20fa1e21568464f10e2a2baa6a
Code:
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import bookService from '#/service/BookService.js';
Vue.use(Vuex)
export default new Vuex.Store({
state: {
books: []
},
mutations: {
SET_BOOKS(books) {
this.state.books = books
}
},
actions: {
fetchBooks(context) {
bookService.getAllBooks()
.then(response => {
console.log(response)
context.commit('SET_BOOKS', response.data)
})
.catch(error => {
console.log(error)
})
}
},
modules: {
}
})
Home.vue(view component) - template is irrelevant
<script>
import BookElement from "#/components/BookElement.vue";
export default {
components: {
BookElement
},
created() {
this.$store.dispatch("fetchBooks");
},
computed: {
books() {
return this.$store.state.books;
}
}
};
</script>
<style scoped>
</style>
BookService.js
import axios from 'axios';
const apiClient = axios.create({
baseUrl: `http://localhost:9001/books`,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
const bookService = {
getAllBooks() {
return apiClient.get()
}
}
export default bookService
I tried the plugin solution(vue-axios), but I don't understand all that hassle just for making a get request that is not even in a .vue file, but in a .js file which I import in my component. The axios package is indeed installed. What I'm expecting is to see in the Home component in the vue devtools that in the books computed property I have the response body.
Edit: I added the code in the question as well.
You didn't pass url to get:
Change it like this:
return apiClient.get('/')
In Vue I am building a small Userprofile page. It is build on token-authentication using Axios. When mounting this page the token is undefined.
with login a token is placed in the localStorage.
The Axios Get request is build outside the Vue component
Api.js
import axios from 'axios'
export default () => {
return axios.create({
baseURL: `http://localhost:8081/`
})
}
Get request
import Api from '#/services/Api'
let config = {
headers: {
'Content-Type': 'application/json',
'Authorization': localStorage.getItem('token')
}
}
export default {
index () {
return Api().get('user/profile', config)
}
}
Vue
<script>
export default {
data: () => ({
user: {}
}),
mounted () {
this.user = UserProfileService.index
console.log(UserProfileService.config)
}
}
</script>
There are many advices and I tried them all. With tics, with commas etc. Who sees the big picture?
Use a request interceptor to set the Authorization header:
// Api.js
export default () => {
const instance = axios.create({
baseURL: `http://localhost:8081/`
})
instance.interceptors.request.use(function (config) {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
}, function (error) {
// Do something with request error
return Promise.reject(error)
})
return instance
}
I added the code from digital drifter, and solved the problem (with help) with changing the mounted function in Vue.
mounted () {
console.log('mounted')
UserProfileService.index()
.then((res) => {
this.user = res.data
})
}
I would like to use $nuxt.$loading https://nuxtjs.org/api/configuration-loading/ outside of Vue component. I created central js for hitting APIs.
services/api-client.js
import axios from "axios";
import { state } from '../store/modules/sessions';
const axiosClient = axios.create({
baseURL: process.env.BASE_URL,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Api-Key': state().token
}
});
axiosClient.interceptors.request.use(function (config) {
// show nuxt loading here
return config;
}, function (error) {
return Promise.reject(error);
});
axiosClient.interceptors.response.use(function (response) {
// hide nuxt loading here
if (response.data.status.code != 200) {
throw { message: response.data.status.errorDetail };
} else {
return response;
}
}, function (error) {
// hide nuxt loading here
return Promise.reject(error);
});
export default {
all(path) {
return axiosClient.get(path);
},
show(path) {
return this.all(path);
},
create(path, params) {
return axiosClient.post(path, params);
},
update(path, params) {
return axiosClient.put(path, params);
}
};
and from my index.vue I'm dispatching the actions which trigger the Api Request.
<template>
<div>
<h1> Welcome </h1>
</div>
</template>
<script>
export default {
created() {
this.$store.dispatch('getInsiders', this);
}
}
</script>
The solution to your problem is this code below.
Please try this.
export default function({ $axios, store }: any) {
$axios.onRequest((config: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.start()
return config
})
})
$axios.onResponse((response: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.finish()
return response
})
})
$axios.onError((error: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.finish()
return Promise.reject(error)
})
})
}
Do you really need to declare own axios client?
Standard way how to do this is using nuxt's axios module and then customize it in your plugin.
nuxt.config.js
modules: ['#nuxtjs/axios'],
plugins: ['~/plugins/axios']
~/plugins/axios
export default ({ $axios, redirect }) => {
$axios.onError(error => {
// do anything you need
})
}
The axios module will manage loading status automatically.
Although you still can disable progress for individual requests
Eg from component/action
await this.$axios.$get('/myapi', { progress: false })