The following code sends a post request to an API and then stores the API's response. The object stored is a fulfilled promise, rather than the body. I though I was using the .then properly, as something similar works for a get request.
The following saga is called every time a post request is made. It contains everything from the post to the call to the call to the reducer.
function* setData(action) {
const url = action.payload.url;
const data_obj = action.payload.data;
console.log(action);
try {
const json = fetch(url, {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(data_obj)
})
.then(statusHelper)
.then(response => response.json());
yield put({ type: "DATA_SENT", payload: json });
}
catch (e)
{
yield put({ type: ERROR_OCCURED, payload: { error : {message: "An Error occured sending HIT data. Please try again later"}}});
}
}
Below is the statusHelper function. It came from another SO answer (I'll edit in the link).
function statusHelper (response) {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
} else {
return Promise.reject(new Error(response.statusText))
}
}
I think your issue is here
.then(response => response.json());
response.json() also returns a promise.
You need to .then off the json() call and return the data.
I was missing a yield after the fetch. This wound up being the solution for me
function* setData(action) {
const url = action.payload.url;
const data_obj = action.payload.data;
let json;
try {
const response = yield fetch(url, {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(data_obj)
});
//const resp = statusHelper(response);
console.log('Response: ', response, response.status)
if(response.status >= 200 && response.status < 300)
{
json = yield response.json();
}
console.log("JSON: ", json);
}
catch (e)
{
yield put({ type: ERROR_OCCURED, payload: { error : {message: "An Error occured sending HIT data. Please try again later"}}});
return;
}
yield put({ type: "DATA_SENT", payload: json });
}
Related
I am using the following fetch post request to create an item in my DB. I am trying to use react-query to detect the error thrown by the request.
export function createItem(id, body, token) {
fetch(`${API_URL}/${id}/items`, {
method: 'post',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
body: JSON.stringify(body)
})
.then(res => {
if (res.ok) {
return res.json()
}
console.log(res.status)
throw new Error("Error creating review")
})
.catch((err) => console.log(err))
}
I have the mutation set like so:
const mutation = useMutation(() => {
return createItem(props.item.id, item, token)
})
And its called with:
<Button disabled={!valid} onPress={() => mutation.mutate()}>
Submit
</Button>
I use this logic to display the error:
{
mutation.isError && <Text>{mutation.error.message}</Text>
}
I see the createItem function errors with a 400 status code which is what I expect but react-query does not set isError to true. Instead isSuccess is true. Am I handling the error wrong some how?
From the react query docs, they return a promise to the mutation, so try to change your function createItem to the following:
export function createItem(id, body, token) {
// return the fetch as a promise
return fetch(`${API_URL}/${id}/items`, {
method: 'post',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
body: JSON.stringify(body)
})
// remove then and catch here
The problem is that you are catching the error inside the mutation function. React Query requires to you to return a resolved or rejected promise from your function.
Promise.catch also returns a Promise. If you don't return anything, it will be a Promise that returns undefined. But that is still a resolved Promise that will be passed to React Query.
So, in short: Don't catch inside the function. Use one of the callbacks that react-query provides for error logging:
export function createItem(id, body, token) {
fetch(`${API_URL}/${id}/items`, {
method: 'post',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
body: JSON.stringify(body)
})
.then(res => {
if (res.ok) {
return res.json()
}
console.log(res.status)
throw new Error("Error creating review")
})
}
const mutation = useMutation(
() => {
return createItem(props.item.id, item, token)
},
{
onError: (error) => console.log(error)
}
)
I am working on an SPA with redux-saga state management. My load and save methods themselves are working, yet there is a lot of weird stuff... Below is the saga code:
export function* getEventDetails({ id }) {
const requestURL = `${url}/admin/event/${id}`
try {
const event = yield call(request, requestURL)
yield put(eventLoaded(event, id))
} catch (err) {
yield put(eventLoadingError(err))
}
}
export function* saveEventDetails({ event }) {
const id = event['id']
const requestURL = `${url}/admin/event/${
!isNaN(id) && id !== undefined && id !== null ? id : 'new'
}`
try {
const createdEvent = yield call(request, requestURL, {
method: !isNaN(id) && id !== undefined && id !== null ? 'PUT' : 'POST',
body: JSON.stringify(event)
})
yield put(eventSaved(createdEvent, createdEvent['id']))
yield put(loadEvent(createdEvent['id']))
yield put(loadPreviousEvents())
yield put(loadUpcomingEvents())
} catch (err) {
console.log('caught error inside saga')
yield put(eventSavingError(err))
}
}
export default function* eventsData() {
yield takeLatest(LOAD_EVENT, getEventDetails)
yield takeLatest(SAVE_EVENT, saveEventDetails)
}
One thing is definitely strange - if I turn off the API server then try saving, I never see caught error inside saga in the console. I am therefore unable to dispatch the eventSavingError action, etc.
Where is my error action? In the console I see:
reducer.js:48 action: {type: "project/Container/SAVE_EVENT", event: {…}}
request.js:55 PUT http://localhost:5000/event/10 net::ERR_CONNECTION_REFUSED
The request function:
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response
}
const error = new Error(response.statusText)
error.response = response
throw error
}
export default function request(url, options) {
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Request-Headers': 'Content-Type, Authorization'
}
const token = localStorage.getItem('token')
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
const newOptions = {
...options,
mode: 'cors',
headers
}
return fetch(url, newOptions)
.then(checkStatus)
.then(parseJSON)
}
Using #oozywaters suggestion, I tweaked the code as:
return fetch(url, newOptions)
.then(checkStatus)
.then(parseJSON)
.catch(err => {
throw err
})
It does fix the problem with the missing exception.
I'm getting my token from an API but unfortunately my API is returning 400 bad request. I've already checked my api via Postman and it's working fine there. Kindly let me know solution or any mistake.
async componentWillMount(){
axios.post('http://api.myapiurl.com/token', {
grant_type: 'PASSWORD',
username: 'MY_USERNAME',
password: 'MY_PASSWORD'
}, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
}).then(response => {
console.log(response.data)
}).catch(err => console.log("api Erorr: ", err.message))
}
error in response below
Request failed with status code 400
- node_modules\axios\lib\core\createError.js:16:24 in createError
- node_modules\axios\lib\core\settle.js:18:6 in settle
- ... 10 more stack frames from framework internals
It is Solved by using QueryString.stringify(). I just pass the body into QueryString.stringify() like below:
axios.post('http://api.apiurl.com/token', QueryString.stringify({
grant_type: 'MY_GRANT_TYPE',
username: 'MY_USERNAME',
password: 'MY_PASSWORD'
}), {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
}
}).then(response => {
console.log(response.data)
}).catch(err => console.log("api Erorr: ", err.response))
From what I can see you are sending json data, but your Content-Type header is set to application/x-www-form-urlencoded; charset=UTF-8. if your api is expecting json then it should be application/json.
try using fetch instead, might be some axios bug, you dont need to add any libraries, here is an example:
fetch("http://api.myapiurl.com/token", {
method: "POST", // *GET, POST, PUT, DELETE, etc.
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
grant_type: "PASSWORD",
username: "MY_USERNAME",
password: "MY_PASSWORD"
})
})
.then(res => {
res.json();
})
.then(data => console.log(data)) // ur data is here
.catch(err => console.log("api Erorr: ", err));
First install the package axios from the url https://www.npmjs.com/package/react-native-axios
Then create two service for handling get and post request so that you can reuse them
GetService.js
import axios from 'axios';
let constant = {
baseurl:'https://www.sampleurl.com/'
};
let config = {
headers: {
'Content-Type': 'multipart/form-data',
'Accept': 'application/json'
}
};
export const GetService = (data,Path,jwtKey) => {
if(jwtKey != ''){
axios.defaults.headers.common['Authorization'] = 'Bearer '+jwtKey;
}
try{
return axios.get(
constant.baseUrl+'api/'+Path,
data,
config
);
}catch(error){
console.warn(error);
}
}
PostService.js
import axios from 'axios';
let constant = {
baseurl:'https://www.sampleurl.com/'
};
let config = {
headers: {
'Content-Type': 'multipart/form-data',
'Accept': 'application/json'
}
};
export const PostService = (data,Path,jwtKey) => {
if(jwtKey != ''){
axios.defaults.headers.common['Authorization'] = 'Bearer '+jwtKey;
}
try{
return axios.post(
constant.baseUrl+'api/'+Path,
data,
config
);
}catch(error){
console.warn(error);
}
}
Sample code for using get and post services is given below
import { PostService } from './PostService';
import { GetService } from './GetService';
let uploadData = new FormData();
uploadData.append('key1', this.state.value1);
uploadData.append('key2', this.state.value2);
//uploadData.append('uploads', { type: data.mime, uri: data.path, name: "samples" });
let jwtKey = ''; // Authentication key can be added here
PostService(uploadData, 'postUser.php', jwtKey).then((resp) => {
this.setState({ uploading: false });
// resp.data will contain json data from server
}).catch(err => {
// handle error here
});
GetService({}, 'getUser.php?uid='+uid, jwtKey).then((resp) => {
// resp.data will contain json data from server
}).catch(err => {
// handle error here
});
Reference from one of my another post Post action API with object parameter within the URL
If you have any doubts, feel free to know
I almost finished creating React Native application, few days ago register action has stopped working.. I'm sending fetch request and it always returns network error altough there is 400 response and message that user exists, it stops there..
I'm destructuring the response and displays api response message instead of fetch network error but now it doesn't work. I'm doing the same for the login action and it works.
Could it be something with multipart/form-data ?
export const register = data => dispatch => {
dispatch(createUser());
const d = new FormData();
d.append("name", data.name);
d.append("email", data.email);
d.append("password", data.password);
d.append("avatar", data.avatar);
fetch(API_URL + "/register", {
method: "POST",
headers: {
"content-type": "multipart/form-data"
},
body:d
})
.then(response => response.json().then(user => ({ user, response })))
.then(({ user, response }) => {
if (!response.ok) {
console.log(response, user)
} else {
console.log(response, user)
}
})
.catch(err => {
throw err;
});
};
The api route works in Postman..
In this case, your using fetch which is Promise based incorrectly,
Try,
fetch(API_URL + "/register", {
method: "POST",
headers: { "content-type": "multipart/form-data" },
body:d
})
.then(response => {
console.log(response, response.json().user)
})
.catch(err => {
console.log(err)
});
Check the logs and see if it shows proper network response and debug from there.
Redux docs suggest to have 3 different actions per AJAX request. For example for Login they would be:
LOGIN_REUQEST
LOGIN_FAILURE
LOGIN_SUCCESS
But I have problem with catching all errors that can be thrown by fetch.
In redux docs I found this example:
return fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(
response => response.json(),
// Do not use catch, because that will also catch
// any errors in the dispatch and resulting render,
// causing an loop of 'Unexpected batch number' errors.
// https://github.com/facebook/react/issues/6895
error => console.log('An error occured.', error)
)
.then(json =>
// We can dispatch many times!
// Here, we update the app state with the results of the API call.
dispatch(receivePosts(subreddit, json))
)
}
But I see several problems in it:
It doesn't use catch, so it wont catch any problems with AJAX request on user side (for example no internet)
It doesn't handle responses with status != 200.
I ended up this this code, but still it won't handle case #1 (because of React issue - see comment in above code):
fetch('/api/auth/signin', {...}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/plain, */*',
}
})
.then ( response =>
response.json().then(json => {
if (response.ok) {
dispatch(loginSuccess(json))
} else {
dispatch(loginFailure(json.errMsg))
}
})
)
Can you please give me any example which handles all possible errors with AJAX request using fetch. Many tutorial and open-source projects just ignoring it.
You don't need .catch() to handle offline errors. The second argument to .then(), the error callback, will catch offline (no internet connection) errors.
And, handling responses that are not 200 is straightforward:
fetch({...})
.then(
response => {
if (response.status !== 200) {
dispatch(loginFailed(response.json()));
return Promise.reject();
} else {
return response.json();
}
},
error => {
dispatch(loginFailed(error.json()));
return Promise.reject();
}
)
.then(json => {
dispatch(loginSuccess(json));
});
async function fetchLists(url, token, userName, etag, dataToUpdate) {
var responseJSON;
try {
let response = await fetch(url,
{
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Access-Token': token,
'X-Key': userName,
'If-None-Match': etag
}
}
);
switch (response.status) {
case 200:
var data = await response.json();
var dataType = getListForUpdate(dataToUpdate);
responseJSON = {
action: dataType,
message: UPDATE_DATA,
response: response,
responseJSON: data
}
break;
case 304:
var dataType = getListUptoDate(dataToUpdate);
responseJSON = { action: dataType, message: DATA_UPTO_DATE };
break;
case 404:
responseJSON = { message: NOT_FOUND };
break;
case 500:
responseJSON = { message: SERVER_ERROR };
break;
case 401:
case 402:
responseJSON = { message: INVALID_CREDENTIALS };
break;
}
// console.log(response);
} catch (err) {
// console.log(err);
responseJSON = { message: NETWORK_REQUEST_FAILED };
// console.log(responseJSON);
}
return responseJSON;
}
This code structure maybe able to solve your problem. I have recorded all the responses and left catch for network request failures. Comment if you don't understand any of it.
Hope it helps.