I have a logging function which logs errors. When an Ajax Request fails with a non JSON data type, the log method should log it, however, we are getting the mutated error as the attached screenshot shows. I am trying to call this log action within a service.
Code
...
import {log} from '../actions/LoggingActions';
...
export default function request(url, opts, dispatch, type = 'application/x-www-form-urlencoded') {
...
return new Promise((resolve, reject) => {
$.ajax(args).then((data) => {
dispatch(httpEndRequest([url, opts, dispatch]));
resolve(data);
}).fail((jqXHR, textStatus, errorThrown) => {
const error = (jqXHR && jqXHR.responseJSON) ?
jqXHR.responseJSON.message :
'Error Making Request';
dispatch(httpFailRequest([url, opts, dispatch], error));
try {
reject(JSON.parse(jqXHR.responseText));
} catch (e) {
console.log(jqXHR.responseText, jqXHR, error);
reject(error);
dispatch(log('Received data is not in JSON format', {requestUrl: url}, {result: e, response: jqXHR, status: textStatus, error: errorThrown}, 'error'));
}
});
});
}
Instead of using jQuery with React, Use axios or fetch (Promise based HTTP clients). I personally prefer axios.
To use axios, do
npm install axios --save. Then
import axios from 'axios';
return new Promise((resolve, reject) => {
axios.get(url, {
params: params
})
.then((response) => {
resolve(response.data);
})
.catch((error) => {
// error.response.status
dispatch(log(error));
reject(error);
});
});
Related
I am working on a React JS project. In my project, I am using React query, https://react-query.tanstack.com/docs/guides/mutations. I am using mutation to make the post request to the server. But I am trying the get the response returns from the server when the API call fails with the onError call back.
This is my code.
let [ createItem ] = useMutation(payload => createItem(payload), {
onSuccess: (response) => {
},
onError: (error) => {
// here I am trying to get the response. In axios, we can do something like error.data.server_error_code
},
onMutate: () => {
}
})
As you can see in the comment, I am trying to read a field returned from the server within the onError callback. How can I do that?
let [ createItem ] = useMutation(payload => createItem(payload), {
onSuccess: (response) => {
},
onError: (error) => {
console.log(error.response.data);
console.log(error.response.status);
},
onMutate: () => {
}
})
It's not entirely clear when just doing console.log(error) inside onError, but error.response should be available.
It should work as it is. Make sure that your HTTP client (probably, Axios) is configured to throw an error. For example:
import axios from 'axios'
import { useMutation } from 'react-query'
import { BASE_URL } from 'constants/api'
const client = axios.create({
baseURL: BASE_URL,
})
const request = (options) => {
const onSuccess = (response) => response
const onError = (error) => {
// Throwing an error here
throw error
}
return client(options).then(onSuccess).catch(onError)
}
const { mutate } = useMutation(
async (data) =>
await request({
url: '/someUrl',
method: 'post',
data
}),
{ onError: (e) => console.log(e) }
)
And of course, it's better to store your Axios settings within a separate file, and then just import the 'request' variable where mutations are using.
If you are using fetch, you have to know that fetch does not throw any error unless is a network problem (as read here)
My solution was just to change to axios (which throws error when 400 or 500), but if you still need to use fetch, you need to find a way to make it throw errors instead.
I think the issue with NOT having an error.response in the callback depends on how the API is failing. If you look at the react-query documentation it shows that most HTTP libs like axios will throw if there is a non 2xx response. However it's up to the underlying API function how it handles that.
For example axios https://axios-http.com/docs/handling_errors will return the response object if there is a response from the server. They will return the request if the call has timed out and return just a message if the previous two don't fit the error
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});
However, if you're using the Fetch API you have handle this yourself. Taken straight from react-query's docs: https://react-query.tanstack.com/guides/query-functions#usage-with-fetch-and-other-clients-that-do-not-throw-by-default
useQuery(['todos', todoId], async () => {
const response = await fetch('/todos/' + todoId)
if (!response.ok) {
throw new Error('Network response was not ok')
}
return response.json()
})
I'm getting above error when I make a request to backend. I have similar code on another project, there is no issue. but here it's causing problems
my code:
import axios from 'axios';
export default function apiCall(method, path, data) {
console.log(method, url, data);
return new Promise((resolve, reject) => {
return axios[method](path, data)
.then(res => {
return resolve(res.data);
})
.catch((err) => {
console.log(err)
reject(err)
});
});
}
api call function
apiCall('POST', `${process.env.REACT_APP_BASE_URL}/`, {standard, subject, totalMarks, totalQuestions} )
.then(data =>{
console.log(data);
})
.catch(err=>{
console.log(err);
return this.props.addError(err.message)
});
Pay attention to the fact that objects in javascript are case sensitive, therefore, accessing obj['post'] and obj['POST'] will return different values.
axios has method get, post etc, as lowercase, you are trying to access them via uppercase, therefore u getting an undefined value.
You can fix that by converting the method variable to lowercase.
import axios from 'axios';
export default function apiCall(method, path, data) {
return new Promise((resolve, reject) => {
return axios[method.toLowerCase()](path, data)
.then(res => {
return resolve(res.data);
})
.catch((err) => {
console.log(err)
reject(err)
});
});
}
BTW, axios methods are already returning Promises, so you can make your code a bit simpler by using it.
import axios from 'axios';
export default function apiCall(method, path, data) {
return axios[method.toLowerCase()](path, data)
.then(res => res.data)
.catch((err) => {
console.log(err);
return Promise.reject(err);
});
}
I had the same issue and felixmosh gave me the key
const { data } = await axios.PUT(
`/api/users/profile/update/`,
user,
config
)
I fixed it changing the method and it worked to me ;)
const { data } = await axios.put(
`/api/users/profile/update/`,
user,
config
)
I'm using a Redux Form to send a POST call to an Express endpoint. The endpoint is supposed to return the successfully saved object, or an error.
The endpoint successfully saves the posted data and returns the JSON. But Axios in the Redux action picks up both the .then and the .catch triggers-in the following action, it logs the following:
successful response: { …}
failure response: undefined
What am I doing wrong?
My Axios action:
export function addPlot(props) {
const user = JSON.parse(localStorage.getItem('user'));
return function(dispatch) {
axios
.post(
`${ROOT_URL}/plots`,
{
props
},
{ headers: { authorization: user.token } }
)
.then(response => {
console.log('successful response: ', response.data);
const plotModal = document.getElementById('plotModal');
plotModal.modal('dispose');
dispatch({ type: PLOT_ADDED, payload: response.data });
dispatch({ type: ADDING_PLOT, payload: false });
dispatch({
type: NEW_PLOT_GEOJSON,
payload: ''
});
})
.catch(response => {
console.log('failure response: ', response.data);
dispatch(authError(PLOT_ADD_FAILURE, 'Failed to add plot'));
});
}
My endpoint:
exports.newPlot = async (req, res, next) => {
console.log(JSON.stringify(req.body.props));
let company;
if (req.user.companyCode !== 'Trellis') {
company = req.user.companyCode;
} else {
company = req.body.props.company;
}
const {
name,
feature,
growerPhone,
plotCode,
rootStock,
region,
variety,
grower,
planted
} = req.body.props;
const plot = new Plot({
name,
grower,
variety,
planted,
region,
rootStock,
plotCode,
growerPhone,
feature,
company
});
try {
const newPlot = await plot.save();
res.json(newPlot);
} catch (e) {
console.log("couldn't save new plot", JSON.stringify(e));
return res.status(422).send({ error: { message: e, resend: true } });
}
};
You could use redux-thunk middleware to manage async actions.
The problem I see is that you are not dispatching the axios action, you must call dispatch(this.props.addPlot(props))in order to do something in the redux store.
I have a common api class that i use for handling api calls in React Native. It will make the call and get the json/ error and return it. See the code below.
// General api to acces data from web
import ApiConstants from './ApiConstants';
export default function api(path,params,method, sssid){
let options;
options = Object.assign({headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}},{ method: method }, params ? { body: JSON.stringify(params) } : null );
return fetch(ApiConstants.BASE_URL+path, options).then( resp => {
let json = resp.json();
if (resp.ok) {
return json;
}
return json.then(err => {
throw err;
}).then( json => json );
});
}
But when i write the jest test to mock the api as folllows in tests folder.
test('Should login',() => {
global.fetch = jest.fn(() => new Promise((resolve) => {
resolve( { status: 201, json: () => (mock_data_login) });
}));
return Api(ApiConstants.LOGIN,{'un':'test1','pwd':'1234'},'post', null).then((data1)=>{
expect(data1).toBeDefined();
expect(data1.success).toEqual(true);
expect(data1.message).toEqual('Login Success');
});
});
it fails with:
TypeError: json.then is not a function
When I change the fetch return to this, the test passes:
return fetch(ApiConstants.BASE_URL+path, options).then( resp => {
let json = resp.json();
return json
});
}
Why is this type error error popping up? I can't change the API module, because that will my redux saga code to change. What should I do?
In your code, json is just an Object and not a Promise, so then is undefined. That's the complain you are getting because you are trying to use undefined as a function. The problem is not in the test but in your code that ha san error. Try the following instead.
return fetch(ApiConstants.BASE_URL+path, options)
.then(resp => resp.json())
.then( json => json)
.catch((error) => error);
});
Edit: oh, just read you can't make changes to the component where the error occurs?
Try converting your fetch like this:
return fetch(ApiConstants.BASE_URL+path, options)
.then(resp => {
let json = resp.json();
if (resp.ok) {
return json;
} else {
throw Error(resp.error) // assuming you have some kind of error from endpoint?
}
})
.then(/*handle your ok response*/)
.catch(/*handle your error response*/);
I faced the same issue, The problem is that you are mocking only response.json as function but it should be a Promise, Like this,
global.fetch = jest.fn(() => new Promise((resolve) => {
resolve( { status: 201, json: () => {
return Promise.resolve(mock_data_login);
}
});
}));
This will return a Promise for you json function.
Hope this fix your problem.
I am trying to use the MSAL library within REDUX for auth but having some trouble. When I make a react only app and do the same thing, I get the access token successfully but trying to use it in REDUX, I always get a timeout when trying to fetch the access token.
function Auth() {
var userAgentApplication = new Msal.UserAgentApplication(*my app id*, null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
});
return new Promise((resolve, reject) => {
console.log('inside the promise');
userAgentApplication.loginPopup(["user.read"]).then((token) => {
console.log("Successfully got id token");
console.log("first token: ", token);
console.log(userAgentApplication.getUser().name);
userAgentApplication.acquireTokenSilent(["user.read"]).then((token) => {
resolve(token);
}, function(error) {
reject(error);
});
}, function (error) {
reject(error);
});
});
}
This is the code I have but I always get the following error Token renewal operation failed due to timeout: null
When I try to do this in plain HTML or react only app, it works perfectly. Any sort of help would be highly appreciated.
see if adding a 'catch' and if condition helps to identify the problem.
function Auth() {
return new Promise((resolve, reject) => {
const userAgentApplication = new Msal.UserAgentApplication(*my app id*, null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
});
console.log('inside the promise');
userAgentApplication.loginPopup(["user.read"])
.then((token) => {
console.log("Successfully got id token");
console.log("first token: ", token);
console.log(userAgentApplication.getUser().name);
if (userAgentApplication.getUser()) {
userAgentApplication.acquireTokenSilent(["user.read"])
.then((token) => {
resolve(token);
})
.catch((error) => {
reject(error);
});
} else {
reject("User not logged in");
}
})
.catch((error) => {
reject(error);
});
});
}