Axios-Make multiple request at once (vue.js) - vue.js

How to make multiple requests in parallel using axios and vue ?

Since axios can be used by React and Vue it is pretty much the same code.
Make sure to read axios docs, you can understand it from there.
Anyway, I am going to show you an example:
<template>
<div>
<button #click="make_requests_handler">Make multiple request</button>
{{message}} - {{first_request}} - {{second_request}}
</div>
</template>
And the script:
import axios from 'axios'
export default {
data: () => ({
message: 'no message',
first_request: 'no request',
second_request: 'no request'
}),
methods: {
make_requests_handler() {
this.message = 'Requests in progress'
axios.all([
this.request_1(), //or direct the axios request
this.request_2()
])
.then(axios.spread((first_response, second_response) => {
this.message = 'Request finished'
this.first_request = 'The response of first request is' + first_response.data.message
this.second_request = 'The response of second request is' + second_response.data.message
}))
},
request_1() {
this.first_request: 'first request began'
return axios.get('you_URL_here')
},
request_2() {
this.second_request: 'second request began'
return axios.get('another_URL', { params: 'example' })
}
}
}

You can pass your asynchronous calls to Promise.all.
As long as each of them returns a promise they will execute at the same time.
I'm using store.dispatch but you could equally use axios calls or fetch.
In this example i'm making the calls when the vue component gets created:
...
async created() {
const templates = this.$store.dispatch(TEMPLATES_LOAD);
const userTemplates = this.$store.dispatch(USER_TEMPLATES_LOAD);
const players = this.$store.dispatch(OTHER_PLAYERS_LOAD);
return await Promise.all([templates, userTemplates, players])
.then(() => {
console.log('Loaded data for form elements');
});
}

Related

how to get local storage token in vue methods property

i've a vue app which requires a token when sending a request each time i try to send a request i keep getting token not defined... here's the error
this how i call my methods property in script tag
<script>
import { mapActions } from "vuex";
import axios from "axios";
export default {
name: "Products",
data() {
return {
addresses: [],
products: []
};
},
methods: {
onDeleteAddress(id, index) {
axios
.delete(`http://localhost:5000/api/addresses/${this.$route.params.id}`,
{
headers: {
Authorization: "Bearer" + token,
"x-access-token": token
}
}
)
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
}
}
};
</script>
this my vue template
Delete
this works in my mounted life cycle hook but when i input it in my methods component i get an error
const token = localStorage.getItem("token");
please how can i get the token stored in my local storage and define it in my vue methods conponent
In first make sure the item is already stored in the localstorage
Second instead of calling it from the localstorage it is better to define it in main.js file as global variable so you can use it free every where
Example
Vue.prototype.$globalData = Vue.observable({ token: localStorage.getItem("token") });
And now you can use it in your methods like this
this.$globalData.token
You can as much as you want variable in the globalData object

Websocket within Vuex module: Async issue when trying to use Vuex rootstate

I'm trying to populate my app with data coming from a websocket in the most modular way possible trying to use best practices etc. Which is hard because even when I have dig very deep for advice on the use of websockets / Vuex and Vue I still can't find a pattern to get this done. After going back and forth I have decided to use a store to manage the state of the websocket and then use that vuex module to populate the state of other components, basically a chat queue and a chat widget hence the need to use websockets for real time communication.
This is the websocket store. As you can see I'm transforming the processWebsocket function into a promise in order to use async/await in other module store actions. The way I see this working (and I'm prob wrong, so PLEASE feel free to correct me) is that all the components that will make use of the websocket module state will wait until the state is ready and then use it (this is not working at the moment):
export const namespaced = true
export const state = {
connected: false,
error: null,
connectionId: '',
statusCode: '',
incomingChatInfo: [],
remoteMessage: [],
messageType: '',
ws: null,
}
export const actions = {
processWebsocket({ commit }) {
return new Promise((resolve) => {
const v = this
this.ws = new WebSocket('xyz')
this.ws.onopen = function (event) {
commit('SET_CONNECTION', event.type)
v.ws.send('message')
}
this.ws.onmessage = function (event) {
commit('SET_REMOTE_DATA', event)
resolve(event)
}
this.ws.onerror = function (event) {
console.log('webSocket: on error: ', event)
}
this.ws.onclose = function (event) {
console.log('webSocket: on close: ', event)
commit('SET_CONNECTION')
ws = null
setTimeout(startWebsocket, 5000)
}
})
},
}
export const mutations = {
SET_REMOTE_DATA(state, remoteData) {
const wsData = JSON.parse(remoteData.data)
if (wsData.connectionId && wsData.connectionId !== state.connectionId) {
state.connectionId = wsData.connectionId
console.log(`Retrieving Connection ID ${state.connectionId}`)
} else {
state.messageType = wsData.type
state.incomingChatInfo = wsData.documents
}
},
SET_CONNECTION(state, message) {
if (message == 'open') {
state.connected = true
} else state.connected = false
},
SET_ERROR(state, error) {
state.error = error
},
}
When I debug the app everything is working fine with the websocket store, I can see its state, the data from the server is there etc. The problem comes when I try to populate other components properties using the websocket. By the time other components need the websocket state this is not ready yet so I'm getting errors. Here's an example of one of my components trying to use the websocket state, I basically call an action from the created cycle method:
<template>
<ul class="overflow-y-auto overflow-hidden pr-2">
<BaseChat
v-for="(chat, index) in sortingIncomingChats"
:key="index"
:chat="chat"
:class="{ 'mt-0': index === 0, 'mt-4': index > 0 }"
/>
</ul>
</template>
<script>
import { mapState } from 'vuex'
import BaseChat from '#/components/BaseChat.vue'
export default {
components: {
BaseChat,
},
created() {
this.$store.dispatch('chatQueue/fetchChats')
},
data() {
return {
currentSort: 'timeInQueue',
currentSortDir: 'desc',
chats: [],
}
},
computed: {
sortingIncomingChats() {
return this.incomingChats.slice().sort((a, b) => {
let modifier = 1
if (this.currentSortDir === 'desc') modifier = -1
if (a[this.currentSort] < b[this.currentSort])
return -1 * modifier
if (a[this.currentSort] > b[this.currentSort])
return 1 * modifier
return 0
})
},
},
}
</script>
This is the chatQueue Vuex module that have the fetchChats action to populate data from the websocket to the APP:
export const namespaced = true
export const state = () => ({
incomingChats: [],
error: '',
})
export const actions = {
fetchChats({ commit, rootState }) {
const data = rootState.websocket.incomingChats
commit('SET_CHATS', data)
},
}
export const mutations = {
SET_CHATS(state, data) {
state.incomingChats = data
},
SET_ERROR(state, error) {
state.incomingChats = error
console.log(error)
},
}
This is where I get errors because "rootState.websocket.incomingChats" is not there yet when its called by the fetchChats module action, so I get:
TypeError: Cannot read properties of undefined (reading 'slice')
I have tried to transform that action into an async / await one but it's not working either, but as I mentioned I'm really new to async/await so maybe I'm doing something wrong here:
async fetchChats({ commit, rootState }) {
const data = await rootState.websocket.incomingChats
commit('SET_CHATS', data)
},
Any help will be really appreciated.
In case somebody have the same problem what I ended up doing is adding a getter to my websocket module:
export const getters = {
incomingChats: (state) => {
return state.incomingChatInfo
},
}
And then using that getter within a computed value in the component I need to populate with the websocket component.
computed: {
...mapGetters('websocket', ['incomingChats']),
},
And I use the getter on a regular v-for loop within the component:
<BaseChat
v-for="(chat, index) in incomingChats"
:key="index"
:chat="chat"
:class="{ 'mt-0': index === 0, 'mt-4': index > 0 }"
/>
That way I don't have any kind of sync problem with the websocket since I'm sure the getter will bring data to the component before it tries to use it.

Internal Server Error: /undefined at the end of url for delete request

Getting undefined/ at the end of url and can't seem to figure out the cause.
using vuejs, vuex and bootstrap-vue
`
HTML:
<template slot="time" slot-scope="row">
<b-button #click="deleteTime(row.value.id)">
Delete
</b-button>
</template>
`
method:
`
deleteTime (id) {
this.$store.dispatch('deleteTime', id)
}
`
Action.vue:
`
async deleteTime ({ commit, state }, time) {
commit('SET_LOADING', true)
const id = state.route.params.pageId
await api.deleteTime(id, time)
commit('SET_LOADING', false)
}
`
api.js:
`
let deleteTime = '/api/apps/:id/content/time/?id=:timeId'
async deleteTime (id, time) {
const url = deleteTime.replace(':id', id).replace(':timeId', time)
return http.delete(url)
}
`
I get this http://127.0.0.0:8000/api/apps/1/content/time/?id=10undefined/ and not sure where is this undefined coming from.
Any help would be appreciated.
Update:
http.delete module:
`
import axios from 'axios'
const http = axios.create({
xsrfCookieName: 'csrftoken',
xsrfHeaderName: 'X-CSRFToken'
})
export default {
async delete (url, id) {
const response = await http.delete(`${url}${id}/`)
return response
}
}
`
Thanks
You are passing a full url with query parameters to your axios delete. I think you only need to remove the id argument:
export default {
async delete (url) {
const response = await http.delete(`${url}`)
return response
}
}
Simply remove the id argument because ${id}/ evaluates to undefined/. You are not passing any id parameter at all.
Easiest:
Simply export http

Mock put requests with mock-axios-adapter

I have simple Vue component that fetches API key when it is created and key can be renewed by clicking on button:
<template>
<div>
<div>{{data.api_key}}</div>
<button ref="refresh-trigger" #click="refreshKey()">refresh</button>
</div>
</template>
<script>
export default {
created() {
axios.get(this.url).then((response) => {
this.data = response.data
})
}
methods: {
refreshKey() {
axios.put(this.url).then((response) => {
this.data = response.data
})
},
}
}
</script>
And I want to test it with this code:
import {shallowMount} from '#vue/test-utils';
import axios from 'axios';
import apiPage from '../apiPage';
import MockAdapter from 'axios-mock-adapter';
describe('API page', () => {
it('should renew API key it on refresh', async (done) => {
const flushPromises = () => new Promise(resolve => setTimeout(resolve))
const initialData = {
api_key: 'initial_API_key',
};
const newData = {
api_key: 'new_API_key',
};
const mockAxios = new MockAdapter(axios);
mockAxios.onGet('/someurl.json').replyOnce(200, initialData)
mockAxios.onPut('/someurl.json').replyOnce(200, newData);
const wrapper = shallowMount(api);
expect(wrapper.vm.$data.data.api_key).toBeFalsy();
await flushPromises()
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.$data.data.api_key).toEqual(initialData.api_key);
done()
});
wrapper.find({ref: 'refresh-trigger'}).trigger('click');
wrapper.vm.$nextTick(() => {
console.log(mockAxios.history)
expect(wrapper.vm.$data.data.api_key).toEqual(newData.api_key);
expect(mockAxios.history.get.length).toBe(1);
expect(mockAxios.history.get[1].data).toBe(JSON.stringify(initialData));
expect(mockAxios.history.put.length).toBe(1);
done();
});
})
});
But it turns out only get request is mocked because i receive:
[Vue warn]: Error in nextTick: "Error: expect(received).toEqual(expected)
Difference:
- Expected
+ Received
- new_API_key
+ initial_API_key"
found in
---> <Anonymous>
<Root>
console.error node_modules/vue/dist/vue.runtime.common.dev.js:1883
{ Error: expect(received).toEqual(expected)
Even worse, console.log(mockAxios.history) returns empty put array:
{ get:
[ { transformRequest: [Object],
transformResponse: [Object],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: [Function: validateStatus],
headers: [Object],
method: 'get',
url: '/admin/options/api.json',
data: undefined } ],
post: [],
head: [],
delete: [],
patch: [],
put: [],
options: [],
list: [] }
I tried to define mockAxios in describe block, and console.log it after iteration - and it turns out that put request was here. But not when I needed it. :)
What am i doing wrong? Maybe there are some ways to check if created callback was called and all async functions inside it are done? Maybe i'm using axios-mock wrong?
This test code should pass:
import {shallowMount, createLocalVue} from '#vue/test-utils';
import axios from 'axios';
import api from '#/components/api.vue';
import MockAdapter from 'axios-mock-adapter';
describe('API page', () => {
it('should renew API key it on refresh', async () => {
const flushPromises = () => new Promise(resolve => setTimeout(resolve))
const initialData = {
api_key: 'initial_API_key',
};
const newData = {
api_key: 'new_API_key',
};
const mockAxios = new MockAdapter(axios);
const localVue = createLocalVue();
mockAxios
.onGet('/someurl.json').reply(200, initialData)
.onPut('/someurl.json').reply(200, newData);
const wrapper = shallowMount(api, {
localVue,
});
expect(wrapper.vm.$data.data.api_key).toBeFalsy();
await flushPromises();
expect(wrapper.vm.$data.data.api_key).toEqual(initialData.api_key);
wrapper.find({ref: 'refresh-trigger'}).trigger('click');
await flushPromises();
console.log(mockAxios.history);
expect(wrapper.vm.$data.data.api_key).toEqual(newData.api_key);
expect(mockAxios.history.get.length).toBe(1);
expect(mockAxios.history.put.length).toBe(1);
})
});
A few notes:
I prefer to chain the responses on the mockAxios object, that way you can group them by URL so it's clear which endpoint you're mocking:
mockAxios
.onGet('/someurl.json').reply(200, initialData)
.onPut('/someurl.json').reply(200, newData);
mockAxios
.onGet('/anotherUrl.json').reply(200, initialData)
.onPut('/anotherUrl.json').reply(200, newData);
If you want to test that you only made one GET call to the endpoint (with expect(......get.length).toBe(1)) then you should really use reply() instead of replyOnce() and test it the way you're doing it already. The replyOnce() function will remove the handler after replying first time and you'll be getting 404s in your subsequent requests.
mockAxios.history.get[1].data will not contain anything for 3 reasons: GET requests don't have a body (only URL parameters), you only made 1 GET request (here you're checking 2nd GET), and this statement refers to the request that was sent, not data you received.
You're using async/await feature, which means you can take advantage of that for $nextTick: await wrapper.vm.$nextTick(); and drop the done() call all together, but since you already have flushPromises() you might as well use that.
You don't need to test that you received initialData in the 1st call with this line:
expect(mockAxios.history.get[1].data).toBe(JSON.stringify(initialData)); since you're already testing it with expect(...).toEqual(apiKey).
Use createLocalVue() utility to create a local instance of Vue for each mount to avoid contaminating the global Vue instance (useful if you have multiple test groups)
and finally, 7. it's best to break this test up into multiple it statements; unit tests should be microtests, i.e. test a small, clearly identifiable behaviour. Although I didn't break the test up for you so it contains as little changes as possible, I'd highly recommend doing it.

Unit testing HTTP request with Vue, Axios, and Mocha

I'm really struggling trying to test a request in VueJS using Mocha/Chai-Sinon, with Axios as the request library and having tried a mixture of Moxios and axios-mock-adaptor. The below examples are with the latter.
What I'm trying to do is make a request when the component is created, which is simple enough.
But the tests either complain about the results variable being undefined or an async timout.
Am I doing it right by assigning the variable of the getData() function? Or should Ireturn` the values? Any help would be appreciated.
Component
// Third-party imports
import axios from 'axios'
// Component imports
import VideoCard from './components/VideoCard'
export default {
name: 'app',
components: {
VideoCard
},
data () {
return {
API: '/static/data.json',
results: null
}
},
created () {
this.getData()
},
methods: {
getData: function () {
// I've even tried return instead of assigning to a variable
this.results = axios.get(this.API)
.then(function (response) {
console.log('then()')
return response.data.data
})
.catch(function (error) {
console.log(error)
return error
})
}
}
}
Test
import Vue from 'vue'
import App from 'src/App'
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
let mock = new MockAdapter(axios)
describe('try and load some data from somewhere', () => {
it('should update the results variable with results', (done) => {
console.log('test top')
mock.onGet('/static/data.json').reply(200, {
data: {
data: [
{ id: 1, name: 'Mexican keyboard cat' },
{ id: 2, name: 'Will it blend?' }
]
}
})
const VM = new Vue(App).$mount
setTimeout(() => {
expect(VM.results).to.be.null
done()
}, 1000)
})
})
I am not sure about moxios mock adaptor, but I had a similar struggle. I ended up using axios, and moxios, with the vue-webpack template. My goal was to fake retreiving some blog posts, and assert they were assigned to a this.posts variable.
Your getData() method should return the axios promise like you said you tried - that way, we have some way to tell the test method the promise finished. Otherwise it will just keep going.
Then inside the success callback of getData(), you can assign your data. So it will look like
return axios.get('url').then((response) {
this.results = response
})
Now in your test something like
it('returns the api call', (done) => {
const vm = Vue.extend(VideoCard)
const videoCard = new vm()
videoCard.getData().then(() => {
// expect, assert, whatever
}).then(done, done)
)}
note the use of done(). That is just a guide, you will have to modify it depending on what you are doing exactly. Let me know if you need some more details. I recommend using moxios to mock axios calls.
Here is a good article about testing api calls that helped me.
https://wietse.loves.engineering/testing-promises-with-mocha-90df8b7d2e35#.yzcfju3qv
So massive kudos to xenetics post above, who helped in pointing me in the right direction.
In short, I was trying to access the data incorrectly, when I should have been using the $data property
I also dropped axios-mock-adaptor and went back to using moxios.
I did indeed have to return the promise in my component, like so;
getData: function () {
let self = this
return axios.get(this.API)
.then(function (response) {
self.results = response.data.data
})
.catch(function (error) {
self.results = error
})
}
(Using let self = this got around the axios scope "problem")
Then to test this, all I had to do was stub the request (after doing the moxios.install() and moxios.uninstall for the beforeEach() and afterEach() respectively.
it('should make the request and update the results variable', (done) => {
moxios.stubRequest('./static/data.json', {
status: 200,
responseText: {
data: [
{ id: 1, name: 'Mexican keyboard cat' },
{ id: 2, name: 'Will it blend?' }
]
}
})
const VM = new Vue(App)
expect(VM.$data.results).to.be.null
VM.getData().then(() => {
expect(VM.$data.results).to.be.an('array')
expect(VM.$data.results).to.have.length(2)
}).then(done, done)
})