I'm currently building a vue app and Im using axios. I have a loading icon which i show before making each call and hide after.
Im just wondering if there is a way to do this globally so I dont have to write the show/hide loading icon on every call?
This is the code I have right now:
context.dispatch('loading', true, {root: true});
axios.post(url,data).then((response) => {
// some code
context.dispatch('loading', false, {root: true});
}).catch(function (error) {
// some code
context.dispatch('loading', false, {root: true});color: 'error'});
});
I have seen on the axios docs there are "interceptors" but II dont know if they are at a global level or on each call.
I also saw this post for a jquery solution, not sure how to implement it on vue though:
$('#loading-image').bind('ajaxStart', function(){
$(this).show();
}).bind('ajaxStop', function(){
$(this).hide();
});
I would setup Axios interceptors in the root component's created lifecycle hook (e.g. App.vue):
created() {
axios.interceptors.request.use((config) => {
// trigger 'loading=true' event here
return config;
}, (error) => {
// trigger 'loading=false' event here
return Promise.reject(error);
});
axios.interceptors.response.use((response) => {
// trigger 'loading=false' event here
return response;
}, (error) => {
// trigger 'loading=false' event here
return Promise.reject(error);
});
}
Since you could have multiple concurrent Axios requests, each with different response times, you'd have to track the request count to properly manage the global loading state (increment on each request, decrement when each request resolves, and clear the loading state when count reaches 0):
data() {
return {
refCount: 0,
isLoading: false
}
},
methods: {
setLoading(isLoading) {
if (isLoading) {
this.refCount++;
this.isLoading = true;
} else if (this.refCount > 0) {
this.refCount--;
this.isLoading = (this.refCount > 0);
}
}
}
demo
I think you are on the right path with dispatch event when ajax call start and finish.
The way that I think you can go about it is to intercept the XMLHttpRequest call using axios interceptors like so:
axios.interceptors.request.use(function(config) {
// Do something before request is sent
console.log('Start Ajax Call');
return config;
}, function(error) {
// Do something with request error
console.log('Error');
return Promise.reject(error);
});
axios.interceptors.response.use(function(response) {
// Do something with response data
console.log('Done with Ajax call');
return response;
}, function(error) {
// Do something with response error
console.log('Error fetching the data');
return Promise.reject(error);
});
function getData() {
const url = 'https://jsonplaceholder.typicode.com/posts/1';
axios.get(url).then((data) => console.log('REQUEST DATA'));
}
function failToGetData() {
const url = 'https://bad_url.com';
axios.get(url).then((data) => console.log('REQUEST DATA'));
}
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<button onclick="getData()">Get Data</button>
<button onclick="failToGetData()">Error</button>
For Nuxt with $axios plugin
modules: ['#nuxtjs/axios', ...]
plugins/axios.js
export default ({ app, $axios ,store }) => {
const token = app.$cookies.get("token")
if (token) {
$axios.defaults.headers.common.Authorization = "Token " + token
}
$axios.interceptors.request.use((config) => {
store.commit("SET_DATA", { data:true, id: "loading" });
return config;
}, (error) => {
return Promise.reject(error);
});
$axios.interceptors.response.use((response) => {
store.commit("SET_DATA", { data:false, id: "loading" });
return response;
}, (error) => {
return Promise.reject(error);
})
}
store/index.js
export default {
state: () => ({
loading: false
}),
mutations: {
SET_DATA(state, { id, data }) {
state[id] = data
}
},
actions: {
async nuxtServerInit({ dispatch, commit }, { app, req , redirect }) {
const token = app.$cookies.get("token")
if (token) {
this.$axios.defaults.headers.common.Authorization = "Token " + token
}
let status = await dispatch("authentication/checkUser", { token })
if(!status) redirect('/aut/login')
}
}
}
This example is accompanied by a token check with $axios and store
Related
I'm learning React and I'm using axios and JWT for authentication. I have written an interceptor to refresh the token automatically:
privateAxios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
const { config, response } = error;
const originalRequest = config;
if (response?.status === 401) {
apiProvider
.refreshToken()
.then(() => {
let headers = getAuthHeaders();
privateAxios.defaults.headers = headers;
originalRequest.headers = headers;
return privateAxios(originalRequest);
})
.catch((err) => {
logout();
return Promise.reject(err);
});
}
return Promise.reject(error);
}
);
On my component I have the following:
api.post(data)
.then(() => {
showSuccessFeedbackForm();
reloadTable();
handleClose();
})
.catch((error) => {
setAlertInfos({
message: JSON.stringify(error.response.data),
severity: "error",
});
setShowAlert(true);
})
.finally(() => {
setIsLoaded(true);
});
My problem is that I want to continue with the component's normal "flow" (i.e., showSuccessFeedbackForm() and reloadTable() and handleClose()) if the token needed to be refreshed (when the code reaches return privateAxios(originalRequest)).
How can I accomplish this?
It looks like you should just have to return the apiProvider.refreshToken()... call. After return privateAxios(originalRequest); returns, then return Promise.reject(error); is executing which causes the front-end to receiving an rejection not a resolution.
Consider this intercepted error which does not throw an error to the frontend which still "resolves":
axios.interceptors.response.use(
(res) => res,
(err) => {
console.log("##### AXIOS ERROR #####");
dispatch(increment());
}
);
Simply changing it to this causes the front-end to catch an error which is what your code is essentially doing:
axios.interceptors.response.use(
(res) => res,
(err) => {
console.log("##### AXIOS ERROR #####");
return Promise.reject();
}
);
I'm having a hard time finding info on how to test this function:
const MyService = {
async stringify (entry, cb) {
try {
const response = await axios.post('localhost:3005/stringify', {
entry
})
cb(null, response.data)
} catch (minificationError) {
if (minificationError.response.status === 500) {
cb('error 1', null)
} else {
cb('error 2', null)
}
}
}
}
I understand I can import axios and mock the .post like this:
axios.post.mockResolvedValue({
data: { some: 'value' }
})
That'd work great if I the MyService was returning the promise... but how do I deal with the callback? Is this a bad practice and should the service be returning the promise and then handle errors in the component functions instead?
Additionally, how would I mock a status code with jest (to test the failed states?)
First, you have to set up mock axios after that you have to call your mockapi's in your test case
const axios = {
post: jest.fn(() => {
return Promise.resolve({
data: {},
});
}),
create: () => axios,
request: {},
defaults: {
adapter: {},
headers: {},
},
interceptors: {
request: {
use() {},
},
response: {
use() {},
},
},
};
Once you setup mock axios then you can access in your test case and return whatever mock response and status code you want.
mockAxios.post.mockImplementation((url) => {
if (url.includes("something")) {
return Promise.resolve({ data:{"response":""}, status: 200 });
}
return Promise.reject(new Error("not found"));
});
I have saved a user_id and token in Async storage and i can retrieve it in via console log. with the retrive function. So i know the set function is working perfectly, the functions in deviceStorage all Async.
The problem comes when trying to use the retrieved user_id & token in my component it returns undefined.
How can i get an item from storage and use it later in my code, i want to use the token and userid for a fetch request. Please help me and highlight the best way to do.
import deviceStorage from "../components/services/deviceStorage";
class Jobs extends Component {
constructor() {
super();
this.state = {
jobsData: [],
isLoading: true
};
}
componentDidMount() {
deviceStorage.retrieveToken().then(token => {
this.setState({
token: token
});
});
deviceStorage.retrieveUserId().then(user_id => {
this.setState({
user_id: user_id
});
});
const opts = {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Token " + this.state.token
}
};
fetch("http://example.com/job/" + this.user_id, opts)
.then(res => res.json())
.then(jobs => {
this.setState({
jobsData: jobs,
isLoading: false
});
console.log(jobsData);
})
.catch(error => {
console.error(error);
});
}
render {}
Code for the retrieve and set
import {AsyncStorage} from 'react-native';
const deviceStorage = {
async storeItem(key, item) {
try {
//we want to wait for the Promise returned by AsyncStorage.setItem()
//to be resolved to the actual value before returning the value
var jsonOfItem = await AsyncStorage.setItem(key, JSON.stringify(item));
return jsonOfItem;
} catch (error) {
console.log(error.message);
}
},
async retrieveItem(key) {
try {
const retrievedItem = await AsyncStorage.getItem(key);
const item = JSON.parse(retrievedItem);
// console.log(item);
return item;
} catch (error) {
console.log(error.message);
}
return
}
};
export default deviceStorage;`
There are two ways to get the data stored in async storage:
(1) Promise method. Here your code does not wait for the block to finish and returns promise which is accompanied by .then clause if the promise resolves and .catch clause if there is error.
(2) Async and Await method. This is more preferred, here your code waits for the execution before proceeding one of the example to refer is below:
retrieveData() {
AsyncStorage.getItem("id").then(value => {
if(value == null){
//If value is not set or your async storage is empty
}
else{
//Process your data
}
})
.catch(err => {
// Add some error handling
});
Second Method example:
async retrieveData() {
try {
let value = await AsyncStorage.getItem("id");
if (value !== null) {
//you have your data in value variable
return value;
}
}
catch (error) {
// Error retrieving data
}
}
your retrieve data storage methods should look like this
retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('TASKS');
if (value !== null) {
// We have data!!
return value;
}
} catch (error) {
// Error retrieving data
}
return null;
};
Adding to the previous solutions
//function to retrieve data
async function retrieveItem(key) {
try {
const retrievedItem = await AsyncStorage.getItem(key); //dataType String
const item = JSON.parse(retrievedItem);//dataType object
return item;
} catch (error) {
console.log(error.message);
}
return
}
//function call
retrieveItem(key).then((value) => {
//unlike normal function call, this waits for the promise to complete
return value;// actual value not the promise
})
.catch((error) => {
console.log('Error: ' + error);
});
I have start my first unit test in react with jest this afternoon. The 5 firsts tests that i have to do are about testing the return functions. No so difficult.
But i have difficulty to understand how to unit test my function login that return something i dont understand yet. Is someone see what i have to put in my action.test.js, show me and explain me ?
How can i unit testing login and what represent the dispatch that return the login function ?
**In action.js**
<pre>
import { userConstants } from '../shared/constants';
import { userService } from '../shared/services';
import { history } from '../shared/helpers';
function request(user) {
return { type: userConstants.LOGIN_REQUEST, user };
}
function success(user) {
return { type: userConstants.LOGIN_SUCCESS, user };
}
function failure(error) {
return { type: userConstants.LOGIN_FAILURE, error };
}
function login(username, password) {
return (dispatch) => {
dispatch(request({ username }));
userService.login(username, password).then(
(user) => {
dispatch(success(user));
history.push('/');
},
(error) => {
dispatch(failure(error));
console.error(error); // eslint-disable-line no-console
},
);
};
}
function logout() {
userService.logout();
return { type: userConstants.LOGOUT };
}
function oldLogin() {
return { type: userConstants.OLD_LOGIN };
}
export const userActions = {
login,
logout,
oldLogin,
};
</pre>
**In service.js**
<pre>
function logout() {
// remove user from local storage to log user out
if (localStorage.getItem('user')) {
localStorage.removeItem('user');
}
}
function handleResponse(response) {
return response.text().then((text) => {
const data = text && JSON.parse(text);
if (!response.ok) {
if (response.status === 401) {
// auto logout if 401 response returned from api
logout();
window.location.reload(true);
}
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
return data;
});
}
function login(username, password) {
return fetch(
'https://mon-api',
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username,
password,
context: {
deviceToken: '1cb1b51d19665cb45dc1caf254b02af',
},
}),
},
)
.then(handleResponse)
.then((user) => {
// login successful if there's a jwt token in the response
if (user.sessionToken) {
// store user details and jwt token in local storage to
// keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user));
}
return user;
});
}
export const userService = {
login,
logout,
};
</pre>
dispatch is a redux action. To be able to test you need to mock it. There are utilities like redux-mock-store that facilitate this task, refer to the following article for more details.
I have following codes from my next.js SPA project. Recommend is a component should also load when the page loads. However, the Recommend loads the html but does not execute the getInitialProps function. I wonder if next.js will only execute getInitialProps from the main page (index.js). If so, how can I also load the content from other parts.
import Layout from '../components/layout'
import Recommend from './recommend'
import axios from "axios";
import React from 'react'
const Index = (props) => (
<Layout>
<Recommend/>
{/*{*/}
{/*console.log(props)*/}
{/*}*/}
</Layout>
);
async function from Recommend
Recommend.getInitialProps = async function () {
console.log("here");
let tracks = {};
await axios.get('http://localhost:4000/playlist/detail', {
params: {
id: 1
},
withCredentials: true
}).then(function (response) {
console.log("success");
console.log(response);
for (let i in response.data.playlist.tracks) {
if (response.data.playlist.tracks.hasOwnProperty(i)) {
tracks[i] = {
song_id: response.data.playlist.tracks[i].id,
song_name: response.data.playlist.tracks[i].name,
album_id: response.data.playlist.tracks[i].al.id,
album_name: response.data.playlist.tracks[i].al.name,
artist_id: response.data.playlist.tracks[i].ar[0].id,
artist_name: response.data.playlist.tracks[i].ar[0].name
// Todo add posters
}
}
}
}).catch(function (error) {
console.log("failed to get recommend playlist");
console.log(error);
});
return {
music: tracks
}
The same codes can execute under index when page loads but not in Recommend when page loads.
Thanks advance.
You cannot use await with then block.
You need to remove your await and return a promise inside your then block.
Recommend.getInitialProps = async function() {
console.log('here');
return new Promise((resolve, reject) => {
axios.get('http://localhost:4000/playlist/detail', {
params: {
id: 1
},
withCredentials: true
})
.then(function(response) {
console.log('success');
console.log(response);
let tracks = {};
for (let i in response.data.playlist.tracks) {
if (response.data.playlist.tracks.hasOwnProperty(i)) {
tracks[i] = {
song_id: response.data.playlist.tracks[i].id,
song_name: response.data.playlist.tracks[i].name,
album_id: response.data.playlist.tracks[i].al.id,
album_name: response.data.playlist.tracks[i].al.name,
artist_id: response.data.playlist.tracks[i].ar[0].id,
artist_name: response.data.playlist.tracks[i].ar[0].name
// Todo add posters
};
}
}
// async success
resolve({ music: tracks });
})
.catch(function(error) {
console.log('failed to get recommend playlist');
console.log(error);
// async failure
reject();
});
});
};