Pass parameter/argument to axios interceptor - javascript

How do I send custom parameters to the axios interceptor? I am using an interceptor like this:
window.axios.interceptors.request.use(function (config) {
if (PASSED_PARAM == true) {
doSomethingAwesome();
}
return config;
}, function (error) {
return Promise.reject(error);
});
I also have a response interceptor that needs to receive the same parameter.

Merge params
axios.interceptors.request.use((config) => {
config.params = {...config.params, my_variable: 'value'}
return config
})

The method suggested by #Laurent will cause axios to wipe out all your other parameters and replace it with my_variable, which is may not exactly what you want.
The proper way of adding default parameters instead of replacing it is like this:
axios.defaults.params = {};
axios.interceptors.request.use(function (config) {
config.params['blah-defaut-param'] = 'blah-blah-default-value';
return config;
}, function (error) {
return Promise.reject(error);
});
This works with axios 0.18.1. It does not work with axios 0.19 due to a regression bug..., I believe it will be fixed soon.

The accepted answer, and the answers on this page seem to have missed what the question is asking.
This question is asking something like "When I call axios can I pass data to the interceptor but not to the server" and the answer is yes. Though it is undocumented and when using typescript you'll have to add a //#ts-ignore.
When you call axios you can pass a config object, (either after the url, or if you're not using a shortcut method like .get/.post the axios function just takes a config object. The good news is that config object is always passed along with the response so you can get to it in the interceptor and in the promise handers.
its available on the response objects as response.config and on the error as error.response.config
//#ts-ignore -- typescript will complain that your param isn't expected on the config object.
axios({
method: "get",
url: '/myapi/gocrazy',
// just piggyback any data you want to the end of config, just don't
// use any key's that axios is already expecting
PASSED_PARAM: true
}
//in the interceptor, config is attached to the object, and it keeps any extra keys you tacked on the end.
window.axios.interceptors.request.use(function (config) {
if (config.PASSED_PARAM == true) {
doSomethingAwesome();
}
return config;
}, function (error) {
return Promise.reject(error);
});
window.axios.interceptors.response.use(function (response) {
if (response.config.PASSED_PARAM == true) {
doSomethingAwesome();
}
return response;
}, function (error) {
if (error.response.config.PASSED_PARAM == true) {
doSomethingElseAwesome();
}
return Promise.reject(error);
});

Working solution
It's actually fairly simple to add parameters to the query with Axios interceptors when you send data.
axios.interceptors.request.use((config) => {
config.params = {my_variable: 'value'}
return config
})

axios allows to pass some additional request parameters:
axios.post('/api', `some body`,
{headers: {'Content-Type': ' text/html;charset=UTF-8'},
param: true});
and interceptor:
this.axios.interceptors.request.use(req => {
console.log(`${req.method}: ${req.param}`); //output: `/api: true`
return req;
});
I have tested it on version: 0.21.1

I ended up using the headers object. Not sure if that is recommended, or if it's anti-pattern. But anyhow it works. I am not entirely sure about how many bytes the header adds to the server request head, but I think it's neglectable.

In addition to the answer from DiamondDrake, the global typings can be overridden to not have to 'ts-ignore' the usage:
declare module 'axios' {
interface AxiosRequestConfig extends OriginalAxiosRequestConfig {
PASSED_PARAM: boolean;
}
}

You cannot pass param but you can update the passed param config. request interceptor logic runs before executing requests. It is kinda middlewares So maybe you need to access to tokens and update the request headers
axios.interceptors.request.use(
(config) => {
// or maybe you need to read the stored cookies
const user = localStorage.getItem("user");
if (user) {
// If user exists get the token
const token = JSON.parse(user).token;
// and then update the headers
config.headers.Authorization = `Bearer ${token}`;
}
// maybe u need to refresh the access tokens, you do it in interceptor
return config;
},
(err) => {
return Promise.reject(err);
}
);

The answer of https://stackoverflow.com/users/12706095/zack is kinda correct.
You should create an axios.d.ts file including the following lines, the rest is done by TypeScript.
import "axios";
declare module "axios" {
export interface AxiosRequestConfig {
/** A custom axios request config param that can be used anywhere now. */
myParam?: boolean;
}
}
Now TypeScript won't bother you anymore when you want to use this custom property anywhere when accessing the AxioRequestConfig, e.g. your interceptor.
See Axios typescript customize AxiosRequestConfig for more info.

Related

Cant send AXIOS request from react test

I hope someone can give me a hint with the following problem.
I am currently working on the frontend for a REST API. I would like to test if I can submit a POST request.
Using the npm test command, the test runs and shows a green tick for this test function. However, no POST request is sent and thus no entry is written to the database.
The function createObject(json) is called correctly during the test and the JSON string passed is also correct. Unfortunately the AXIOS POST method is not called.
When I click on "Post Object" via the browser the AXIOS method is called and an object is created in the database.
PostClient.js
import axios from 'axios';
const options = {
headers: {
// 'Content-Type': 'application/x-www-form-urlencoded'
'Content-Type': 'application/json',
'Accept': 'application/json',
} };
export class PostClient {
// This function is called by the test, but the Axios command is not.
static createObject(json) {
const response = axios.post('http://localhost:8080/object/create/', JSON.stringify(json), options)
.then(response => {
console.log(response.data);
return response;
}).catch(function (error) {
console.log(error);
});
return response;
}
}
App.test.js
describe('Test', function(){
let id;
it('addObject()', function () {
const response = PostClient.createObject(objectJSON);
this.id = response.id;
expect(response.status == 200);
});
});
App.js
class App extends React.Component {
render() {
return (
<>
<h2>createObject</h2>
<div className="createObject">
<button onClick={() => PostClient.createObject(objectJSON)}>Post Object</button>
</div>
...
</>
);
}
}
export default App;
First: read the comment by Yevgen Gorbunkov :)
Second: axios.post() method returns a Promise - you are returning that promise instead of returning the result of your request.
My advice is to review how promises work; MDN has a nice article - and maybe brush up on asynchronous code in general.
The quick and dirty solution might be to turn your function into an async function and use async/await:
static async createObject(json) {
// Note: this might throw an error; try...catch is a good idea
const response = await axios.post(url, payload, options);
return response.data;
}

axios.post not returning data from server: "Cannot destructure property 'data' of '(intermediate value)' as it is undefined"

I am trying to get data from server via axios.post().
Decided to use POST and not GET because I want to send an array with ids to look up in the database, which might be too large to fit in GET query params.
I managed to send an array with ids in the body of the POST. This reaches my server. I can successfully find the items in the data base. The items are then returned in the response. The data shows up in Chrome devtools > Network (status 200). I also get the right stuff back when sending a request manually using Postman.
Everything seems to be working fine, but the response does not arrive in my data variable in the axios function.
I spent the day trying out the solutions to all the similar answers here. Nothing worked...
I also tried GET and sending the ids in query params instead, which gives the same error. I suspect I am doing something wrong with async/await because I am getting this "intermediate value" thingy.
Thanks in advance for the help.
CLIENT axios functions
const url = 'http://localhost:5000';
export const getStuff = Ids => {
axios.post(
`${url}/cart/stuff`,
{
Ids: Ids,
},
{
headers: {
'Content-Type': 'application/json',
},
}
);
};
CLIENT actions
import * as api from '../api';
export const getStuff = Ids => async dispatch => {
try {
// Ids is an array like ["5fnjknfdax", "5rknfdalfk"]
const { data } = await api.getStuff(Ids);
// this gives me the error in the title, data never comes through
//dispatch(-dolater-);
} catch (error) {
console.log(error);
}
};
SERVER controllers
export const getStuff = async (req, res) => {
try {
const { Ids } = req.body;
const stuff = await STUFF.find().where('_id').in(Ids);
console.log('SERVER', stuff);
// this works until here. request comes through and
// I can successfully find the stuff I want in the database
res.status(200).json(stuff); // this also works, response is being sent
} catch (error) {
res.status(404).json({ message: error });
}
};
SERVER routes
router.post('/cart/stuff', getStuff);
You have some extra curly braces here (or a missing return, depending on how you look at it). When you use a lambda (arrow function) with curly braces, you have to explicitly return a value or else it will return undefined. Change your code from this:
export const getStuff = Ids => {
axios.post(...);
};
to one of these:
// Option 1
export const getStuff = Ids => {
return axios.post(...);
};
// Option 2
export const getStuff = Ids => axios.post(...);
Either format will return the actual axios promise, instead of the default undefined.
export const fetchPost = () => {
return axios.get(url);
};
This works for me!!

Catch axios requests in different files / functions

I use HTTP requests to get data for my Vue.js application. I have one file called Api.js with the base axios instance:
export default () => {
return axios.create({
baseURL: apiURL,
headers: {
Authorization: `JWT ${store.state.token}`
}
})
}
than I have a file called service.js, which contains the functions for the different endpoints:
export default {
status() {
return Api().get('status/')
}
}
In the .vue file I call the method like that.
created() {
Service.status()
.then(response => {
// do something with the data
})
.catch(e => {
// show exception
})
}
Some exceptions should be handled in Api.js (for example: 401), some other exceptions should be handled in service.js and others in the .vue file. How can I do that?
Disclaimer: I have created two small axios plugins to achieve this specific pattern easily.
axios-middleware
Simple axios HTTP middleware service to simplify hooking to HTTP requests made through Axios.
It uses axios interceptors as mentioned by acdcjunior but it abstracts the use of axios with a commonly known middleware pattern so your app doesn't need to know and deal with the interceptor syntax.
// import your API's axios instance
import http from './api';
import { Service } from 'axios-middleware';
// Create a new service instance
const service = new Service(http);
// We're good to go!
export default service;
You can then use this middleware service to register different middlewares anywhere in your app. A middleware can be as simple as an object or a reusable, easily testable class.
import i18n from './services/i18n';
import toast from './services/toast';
import service from './services/middleware';
import { ApiErrorMiddleware, OtherMiddleware } from './middlewares';
// Then register your middleware instances.
service.register([
// Middleware class instance
new ApiErrorMiddleware(i18n, toast),
new OtherMiddleware(),
// or a simple object
{
onRequest() {
// handle the request
},
onResponseError(error) {
// handle the response error
}
}
]);
Where the ApiErrorMiddleware would be a simple class with the sole responsibility of showing toast messages on error.
export default class ApiErrorMiddleware {
/**
* #param {VueI18n} i18n instance
* #param {Object} toast message service
*/
constructor(i18n, toast) {
this.toast = toast;
this.i18n = i18n;
}
/**
* #param {Object} error
*/
onResponseError(error = {}) {
const { response } = error;
let key = 'errors.default';
if (response && this.i18n.te(`errors.${response.status}`)) {
key = `errors.${response.status}`;
} else if (error.message === 'Network Error') {
key = 'errors.network-error';
} else {
// TODO log unhandled errors
}
this.toast.error(this.i18n.t(key));
}
}
axios-resource
Simple axios resource class to easily interact with a REST endpoint.
Define a resource class. Here, I added onError and onFetchError as examples for your use-case.
import Resource from 'axios-resource';
export default class UserResource extends Resource {
static URL = 'user/{id}';
// This calls `sync` in the background
fetch() {
return super.fetch.apply(this, arguments)
.catch(err => this.onFetchError(err));
}
onFetchError(err) {
// An error occurred while fetching this resource.
}
onError(err) {
// An error occurred with this resource
}
// called for every actions (fetch, create, patch, delete)
sync() {
return super.sync.apply(this, arguments)
.catch((err) => this.onError(err))
}
}
Then, in api.js, create an instance.
import UserResource from './user';
const user = new UserResource();
// GET https://example.com/api/user/me
user.fetch('me')
.then(({ data }) => {
console.log('User data:', data);
});
The error can be dealt with at every step.
in the onFetchError of this specific resource
in the onError of this resource
in a middleware for the app.
You should add axios interceptors:
Axios Interceptors
You can intercept requests or responses before they are handled by
then or catch.
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
Those can (should) be in your Api.js.

React - Return object from helper method get request

I have a function that is called in many place of my app so I am trying to turn it into a helper method and just import where needed. I can't seem to get a response though from where I am calling it. Where is my syntax wrong, or maybe my approach is off entirely?
I saw the post on how to return from an Async AJAX request. This doesn't cover my problem. I know how to return the response. I just don't know how to do it from one file to another. That's my issue.
-- Help Function
export function enforceEmployeeAuth() {
let response;
API.get('user/employee_auth', {}, function(res) {
response = res
return response
})
}
Where it's called
componentDidMount() {
let auth = enforceEmployeeAuth();
// auth is undefined
}
Original Function
enforceEmployeeAuth() {
API.get('user/employee_auth', {}, function(res) {
this.setState({
employee_auth: res.employee_auth,
company_admin: res.company_admin
});
}.bind(this));
}
the answer depends on the case if API supports promises or not.
That's how i would deal with both scenarios:
1) Callback only:
--- Helper function:
export function enforceEmployeeAuth(cb) {
API.get('user/employee_auth', {}, cb)
}
--- Component
componentDidMount() {
enforceEmployeeAuth(res => this.auth = res);
}
2) Promises are supported
--- Helper function:
export function enforceEmployeeAuth() {
return API.get('user/employee_auth', {});
}
--- Component
componentDidMount() {
enforceEmployeeAuth().then(res => this.auth = res);
}
You also might want to use setState in the callbacks, so your component re-renders when you get the async data.

How to wait for ajax call from main Vue instance?

I currently have VueJS components that makes an ajax call to github like so:
(Child) component
Vue.http.get('user/repos').then((response) => {
console.log(response);
}, (response) => {
console.log(response);
});
The problem is that I first need to get an access token before I can make this ajax call. This access token is stored in the database so my main Vue component is making the ajax call to set a common header to all ajax calls:
Main Vue instance
Vue.http.headers.common['Authorization'] = `token ${this.token}`;
const app = new Vue({
el: '#app',
data: {
token: ''
},
created() {
Vue.http.get('/token').then((response) => {
this.token = response.data.token;
}, () => {
console.log('failed to retrieve the access token for the logged in user.');
})
}
});
How can I be sure that before running the ajax call from my component that the ajax call to set the 'Authorization' header has been successful?
Adding this for anyone else who could benefit.
Get the token from the API call, add it up in a vuex state variable.
Access the same using a getter in the child component, as a computed property or you can pass it on as props or via an event bus but both the ways are not as powerful as using vuex.
watch over the property, and perform your required action when the token is obtained.
// Add this up in the child component
computed: {
...mapGetters({
token: <name-of-the-getter> // token becomes the alias for the computed
}) // property.
},
watch: {
token () {
if(this.token) this.someAPICall()// or some other applicable condition
}
},
methods: {
...mapActions({
someAPICall: <name-of-the-action>
})
}
// ----------------------------------------------
Watch requires the value to change, I have noticed that commits made in an action cause the watch to trigger. So if for some reason the token is lost, or expires you will naturally not be able to make the subsequent requests.
EDIT
import store from 'path/to/store'
axios.interceptors.response.use(function (response) {
// extract the token from the response object
// save the token to the store for access during subsequent
// requests.
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
axios.interceptors.request.use(function (config) {
// use store getters to access token
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
You can replace/proxyfiy Vue.http.get function by your own function that will request token first and then do your request, rough idea:
!function()
{
var vue_http_get = Vue.http.get;
var token = null;
// patching/proxying Vue.http.get function
Vue.http.get = function get() {
vue_http_get.apply(Vue.http,"path/to/get/token/").then(function(resp){
token = resp;
Vue.http.headers.common['Authorization'] = ...;
// putting back original Vue.http
Vue.http = vue_http_get;
return Vue.http.get(arguments[0]);
});
};
}();

Categories