I'm making a GET request with Axios in a React-Redux project, and I get the following error:
TypeError: "config.method.toLowerCase is not a function"
request Axios.js:43
wrap bind.js:11
apiCall api.js:32
apiCall api.js:29
... ...
api.js is a file from my own project. bind.js and Axios.js is from the Axios library. This is my api function:
export function apiCall(method, path, data){
let url = backendDomain + path
let config = {
method: [method],
url: [url],
data : [data],
headers:{
"Content-Type":"application/json",
"Accept":"application/json"
}
}
return new Promise((resolve, reject)=>{
return axios(config).then(res=> {
return resolve(res.data)
}).catch(err => {
return reject(err.response);
})
})
The function that makes use of apiCall() is this function:
export function authUser(url, userData, method){
return (dispatch) => {
return new Promise((resolve, reject)=>{
return apiCall(method, "/"+`${url}`, userData)
.then((data) => {
...
resolve();
})
.catch(err=>{
...
reject();
})
})
}
}
Do you think there's something wrong with my code or is there something wrong with the library? When I use authUser to dispatch my action (for Redux State), I double checked that "method" is a String, I console.logged typeof method in api.js, and it returned string.
Edit:
I tried calling toString() to the method parameter passed into apiCall(), but it didn't work:
let reMethod = method.toString();
const config = {
method: [reMethod],
url: [url],
data : [data],
headers:{
"Content-Type":"application/json",
"Accept":"application/json"
}
}
As mentioned in my comment, you're providing an array when axios expects a string:
const config = {
method: reMethod,
url: url,
data : data,
headers:{
"Content-Type":"application/json",
"Accept":"application/json"
}
}
Related
On the following StackBlitz:
https://stackblitz.com/edit/react-jbthdw?file=src%2FApp.js
I have a code which fetches a JSON data with a list of names.
There are two ways of returning data: Axios and jQuery.
With the Axios approach the code works properly.
Now, because some business decisions I need to replace Axios with jQuery.
My problem is: with jQuery the response.data is fetched as string when it should be as object as it happens with Axios.
Below you have the code, but feel free to play with the StackBlitz playground I provided above:
import React from 'react';
import axios from 'axios';
import $ from 'jquery';
import './style.css';
export default () => {
const jsonSource = 'https://raw.githubusercontent.com/tlg-265/mockend/master/data.json';
const handleClickAxios = () => {
axios.get(jsonSource).then(response => {
console.log({ responseType: (typeof response.data)});
console.log(response.data);
});
};
const handleClickJQuery = () => {
sendApiRequest({ url: jsonSource, method: 'get' }).then(response => {
console.log({ responseType: (typeof response.data)});
console.log(response.data);
});
};
return (
<div>
<h1>Axios vs jQuery</h1>
<p><b>Inconsitency:</b> returning response as Object vs String</p>
<button onClick={handleClickAxios}>Axios</button>{' '}
<button onClick={handleClickJQuery}>jQuery</button>
</div>
);
};
export const sendApiRequest = ({
url,
method,
data,
timeout,
}) => {
method = method.toUpperCase();
let additionalSettings = { data: data };
if (method === 'POST') {
additionalSettings = {
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(data),
};
}
return new Promise((resolve, reject) => {
$.ajax({
url,
method: method,
timeout,
...additionalSettings,
success: (response) => {
resolve({
data: response
});
},
error: ({ responseJSON }) => {
reject(responseJSON);
},
});
});
};
Here you have a screenshot of the issue:
Any idea on what do I need to update on the code in order to get: response.data as object with jQuery? I need to get that without any post processing of the response. Is there maybe any params I could use with jQuery so it behaves similarly to Axios?
Thanks!
The URL you are requesting includes this response header:
content-type: text/plain; charset=utf-8
Which is why jQuery isn't processing it as JSON automatically.
A quick search of the documentation for the word "json" quickly brings up the dataType option which lets you override the content-type.
const jsonSource = 'https://raw.githubusercontent.com/tlg-265/mockend/master/data.json';
jQuery.ajax({
url: jsonSource,
dataType: 'json'
}).then(data => {
console.log({
responseType: (typeof data)
});
console.log(data);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
It gets complicated to me when I mix the promise with subscribe and another async task together.
This is my auth service:
getCurrentUserToken(){
return new Promise((resolve,reject)=>{
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
resolve(idToken)
}).catch(function(error) {
reject(error)
});
})
}
This is my HTTP service:
sendEmail(email) {
return this.authService.getCurrentUserToken().then(token => {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic server-Password',
})
};
let data = email
data['idToken'] = token
return this.http.post(this.apiServer + 'sendEmail', data, httpOptions)
})
}
This is how I call the sendEmail(email) function at the component:
Observable.fromPromise(this.httpService.sendEmail(element)).subscribe(
data3 => {
console.log(data3)
}, error => {
console.log(error)
}
))
I have to pass currentUserToken to the API to let the API authenticate the user session. Still, both of the the getCurrentUserToken() sendEmail() are running in async, so I have to use Promise to pass the Token to sendEmail() function, and let the sendEmail function to call the API to send the email.
Without the promise, I am able to subscribe to the http.post like this:
this.httpService.sendEmail(element).subscribe(
data3 => {
console.log(data3)
}, error => {
console.log(error)
}
))
Unfortunately, I screwed it up when I added the promise into it, and the console.log is returning this:
Observable {_isScalar: false, source: Observable, operator: MapOperator}
Please advise on how to subscribe to the http.post that is placed inside the Promise.
There's seriously no need of Complicating things here.
I'll use async/await syntax here and for that, we'll have to work with Promises instead of Observables. Good thing is, we can leverage the toPromise() method on an Observable value to change it to a Promise
Focus on my comments in the code as well
Here's the implementation
For getCurrentUserToken
getCurrentUserToken() {
return firebase.auth().currentUser.getIdToken(true);
// This will already return a Promise<string>
// So no need to do a .then and then return from there.
}
For sendEmail
async sendEmail(email) {
// Since getCurrentUserToken returns a Promise<string> we can await it
const token = await this.authService.getCurrentUserToken();
// token will now have the Current User Token
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic server-Password',
})
};
let data = email
data['idToken'] = token
return this.http.post(this.apiServer + 'sendEmail', data, httpOptions).toPromise();
// Notice how we're calling the .toPromise() method here
// to change Observable into a Promise
}
How to use it?
This code will go in your Component Method where you were previously calling this.httpService.sendEmail. DO MAKE SURE TO MARK THAT FUNCTION AS async THOUGH.
// We can only await something in a function which is declared of type async
async sendEmail() {
try {
const data = await this.httpService.sendEmail(element);
// Since sendEmail again returns a Promise, I can await it.
console.log(data);
} catch (error) {
console.log(error);
}
}
Why don't we use Observable instead of Promises here.
getCurrentUserToken() {
return new Observable(obs => {
firebase
.auth()
.currentUser.getIdToken(/* forceRefresh */ true)
.then(function(idToken) {
obs.next(idToken);
obs.complete();
})
.catch(function(error) {
obs.error(error);
});
});
}
sendEmail(email): Observable {
return new Observable(obs => {
this.authService.getCurrentUserToken().subscribe(token => {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
Authorization: 'Basic server-Password'
})
};
let data = email;
data['idToken'] = token;
this.http
.post(this.apiServer + 'sendEmail', data, httpOptions)
.subscribe(
result => {
obs.next(result);
obs.complete();
},
error => {
obs.error();
}
);
});
});
}
// now call the service from Component like this.
this.httpService.sendEmail(element).subscribe(
data3 => {
console.log(data3)
}, error => {
console.log(error)
}
));
In my project, I use this modified axios function to make an API request:
// api.js
function request (method, path, { query, params }, fields) {
query = escapeQuery(query); // make query string
return axios[method](`${API.URL}${path}?${query}`, {
...fields,
params
});
}
let methods = {};
['post', 'get', 'patch', 'delete', 'put'].forEach(method => {
methods[method] = (...args) => request(method, ...args);
});
So in actual case, like this:
import API from 'api.js';
...
getSomePages (state) {
var config = {
headers: { 'x-access-token': state.token }
};
let query = { 'page': state.selected_page };
return new Promise((resolve, reject) => {
API.get(`/myapipath`, {query}, config).then(...);
});
},
...
When I call API.get() like the above, it works very well. The problem is when I call API.post. It seems that it cannot distinguish the correct field. For example, when I call:
likePost (state, post_id, user_id) {
var config = {
headers: {
'x-access-token': state.token,
'Content-Type': 'application/x-www-form-urlencoded'
}
};
var data = {
post_id, user_id
};
return new Promise((resolve, reject) => {
API.post(`/myapipath`, {data}, config).then(...);
});
},
This API.post call keeps failing, so I looked up the request and found that it doesn't have headers - all the fields I've sent are in the request body.
So I tried something like:
API.post(`/myapipath`, {}, config, data)...
API.post(`/myapipath`, data, config)...
API.post(`/myapipath`, {data, config})...
etc...
but it all failed. It looks like they all cannot understand which is the header. How can I solve this?
I'm having some problems getting a response from a chained Promise.
I have my component where the chain starts
component
componentDidMount = async ()=> {
try{
const products = await post('payments/getProducts', {});
console.log(products);
} catch(e) {
console.log(e)
}
}
This component calls my API helper:
async function post(url, data) {
token = null;
if (firebase.auth().currentUser) {
token = await firebase.auth().currentUser.getIdToken();
}
try {
const response = axios({
method: 'POST',
headers: {
Authorization: `${token}`,
},
data,
url: `${API_URL}${url}`,
})
return response;
} catch(e){
Promise.reject(e);
}
}
and my API Helper then calls a Firebase Cloud Function which calls Stripe:
paymentRouter.post('/getProducts', (req, res) => {
return stripe.products.list()
.then(products => {
console.log(products.data)
return products.data;
})
.catch(e => {
throw new functions.https.HttpsError('unknown', err.message, e);
})
})
Calling the function is no problem, and my Cloud Function logs out the product data, but I can't get the response to log in my API Helper nor my component.
Promise.reject(e);
That is completely senseless as it creates a new rejected promise that is not used anywhere. You could await it so that it gets chained into the promise returned by the async function, or you just return the promise from axios:
async function post(url, data) {
let token = null; // always declare variables!
if (firebase.auth().currentUser) {
token = await firebase.auth().currentUser.getIdToken();
}
return axios({
method: 'POST',
headers: {
Authorization: `${token}`,
},
data,
url: `${API_URL}${url}`,
});
}
Now the errors don't go into nowhere anymore and you can probably debug the problem :)
So I'm using React with React-Router.
I have a onEnter hook which checks if the user is authenticates yes/no and executes the desired action.
export function requireAuth(nextState, replaceState) {
if (!isAuthenticated()) {
if (!Storage.get('token')) replaceState(null, '/login');
return delegate().then(() => replaceState(null, nextState.location.pathname));
}
if (nextState.location.pathname !== nextState.location.pathname) {
return replaceState(null, nextState.location.pathname);
}
}
When the token is expired I call a delegate function which looks like:
export function delegate() {
const refreshToken = Storage.getJSON('token', 'refresh_token');
return getData(endpoint)
.then(res => {
Storage.set('token', JSON.stringify({
access_token: res.data.access_token,
refresh_token: refreshToken,
}));
});
}
The delegate function indeed refresh the tokens in the localStorage. But the requests after the replaceState are not using the updated token, but the previous one. I think this is a async issue, someone knows more about this?
Edit: The function where I use the token:
function callApi(method, endpoint, data) {
return new Promise((resolve, reject) => {
let headers = {
'Accept': 'application/json',
'X-API-Token': Storage.getJSON('token', 'access_token'),
};
const body = stringifyIfNeeded(data);
const options = { method, headers, body };
return fetch(endpoint, options)
.then(response =>
response.json().then(json => ({ json, response }))
).then(({ json, response }) => {
if (!response.ok) {
reject({ json, response });
}
resolve(json);
}).catch((error, response) => {
reject({ error, response });
});
});
}