Error responses from api are considered success in react query - reactjs

In React query every responses are considered as success.
Axios is used to call api request. Here is a axios component.
export const callAxios = async ({
url,
method,
data,
headers,
params,
responseType,
}: CallAxiosAPI) => {
const config: AxiosRequestConfig = {
method: method || 'GET',
url: `${baseUrl}${url}`,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
Authorization: accessToken !== null ? `Bearer ${accessToken}` : '',
...headers,
},
data,
params,
responseType,
}
return axios(config)
.then((res: AxiosResponse<any, any>) => {
return res
})
.catch(err => {
return err
})
}
Here is sample of using useMutation
const adjustProfit = useMutation(
['adjustProfit'],
(params: { configurationId: string; configurationPriceId: number; data: IAdjustType }) => {
return PricingQueries.adjustProfit(
parseFloat(String(params.configurationId)),
params.configurationPriceId,
params.data,
)
},
{
onSuccess: () => {
refetch()
},
onError: () => {
toast.error(t(`message.adjust_price_failed`))
},
},
)
Even there is error onSuccess is called.

The problem is the .catch. By catching an error because you want to log and then not re-throwing the error, you are transforming the error into a resolved Promise. So you're actually never returning the error to useQuery.
The fix is to remove the catch and use the onError callback for logging, or re-throw the error after logging.
This gotcha is so common that I have added it to my FAQ post:
https://tkdodo.eu/blog/react-query-fa-qs#why-do-i-not-get-errors-

Related

React-query mutation isError not setting true on failed POST

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)
}
)

update the state of my component with the response data after Post request with axios

I'm trying to update the state of my component with the response data after Post request with axios but it returns an empty array when I log out the updated state with console.log(), but shows the response.data information received with .then in axois in the broswer console. Please help me out
Code starts here
const [offers, setOffers] = useState({});//THIS IS THE STATE
const search async (e) => {
e.preventDefault();
const options = {
url: "localhost:8080/api/search",
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
data,
};
axios(options)
.then((response) => {
console.log(response.data.data);// THIS RETURNS OBJECT DATA GOTTEN FROM THE SERVER AFTER POST REQUEST
setOffers(response.data.data); //IT DOES NOT UPDATE WITH RESPONSE DATA
console.log(offers); = IT RETURNS AND EMPTY ARRAY
})
.catch(function (error) {
if (error.response) {
setValerr(error.response.data.errors);
console.log(error.response);
}
});
};
thanks in advance
In react, setState is asynchronous, so when you call "setOffers" it is an asyncronous action.
Therefore when you call console.log, offers might not be updated yet.
You can read more about it here:
https://reactjs.org/docs/faq-state.html#when-is-setstate-asynchronous
To listen to the value of "offers" you might need to use useEffect
An example
const [offers, setOffers] = useState({}) //THIS IS THE STATE
const search = async (e) => {
e.preventDefault()
const options = {
url: 'localhost:8080/api/search',
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json;charset=UTF-8',
},
data,
}
axios(options)
.then((response) => {
console.log(response.data.data) // THIS RETURNS OBJECT DATA GOTTEN FROM THE SERVER AFTER POST REQUEST
setOffers(response.data.data) //IT DOES NOT UPDATE WITH RESPONSE DATA
console.log(offers)
})
.catch(function (error) {
if (error.response) {
setValerr(error.response.data.errors)
console.log(error.response)
}
})
}
useEffect(() => {
// This should log offers to the console if it has been set
if(offers) {
console.log(offers)
}
}, [offers])

useSWR - How to pass config object to fetch

I'm trying to integrate useSWR in a next js project I'm working on.
I want to pass a config to fetcher as an argument. I have read about Multiple Arguments in the docs
but it's not returning the data for some reason. it is making the api request I can see that in the network tab.
not sure how to do this.
any suggestions?
const fetcher = async (url, config) => {
let res;
if (config) {
res = await fetch(url, config);
} else {
res = await fetch(url);
}
if (!res.ok) {
const error = new Error('An error occurred while fetching the data.');
error.info = await res.json();
error.status = res.status;
throw error;
}
return res.json();
};
const { data, error } = useSWR(
[
rolesUrl,
{
headers: {
Authorization: `Bearer ${user.token}`,
'Content-Type': 'application/json',
},
},
],
fetcher
);
After a very long debuging I found out. fetch is getting the config object.
and then makes the request to the api. then useSWR returns the response. which causes the component to re-render. the config object gets recreated.
useSWR thinks argument updated and make the api request again. that's why we don't get the data.
I have fixed this with useMemo hook
const config = useMemo(
() => ({
headers: {
Authorization: `Bearer ${user.token}`,
'Content-Type': 'application/json',
},
}),
[user.token]
);
const { data, error } = useSWR([rolesUrl, config], fetcher);

Make multiple axios calls using jest

I am working with rest js using typescript and I am trying to mock multiple API calls using jest for unit testing.
My api calls are in the following format:
await axios.request({
method: 'POST',
url: //api url,
data: {},
headers: {}
})
.then()
.catch()
I am mocking the axios as follows:
jest.mock('axios', () => {
return {
request: jest.fn().mockResolvedValue({
data: ['responseData', 'responseData1']
headers: //response header
})
}
});
The test case for api call is created as follows:
expect(axios.request).toHaveBeenCalled(); expect(axios.request).toHaveBeenCalledWith({
method: 'POST',
url: //api url,
data: {},
headers: {}
});
For multiple API calls, I am mocking it multiple times with different response data but it is taking the last mocked value as the response of all the API calls in the test cases.
for example: for multiple data mocks like:
jest.mock('axios', () => {
return {
request: jest.fn().mockResolvedValue({
data: ['responseData', 'responseData1']
headers: //response header
})
}
});
jest.mock('axios', () => {
return {
request: jest.fn().mockResolvedValue({
data: ['secondResponseData', 'secondResponseData1']
headers: //response header
})
}
});
when I am running the test cases I am getting the response for all my apis as:
data: ['secondResponseData', 'secondResponseData1']
headers: //response header
data: ['secondResponseData', 'secondResponseData1']
headers: //response header
instead of:
data: ['responseData', 'responseData1']
headers: //response header
data: ['secondResponseData', 'secondResponseData1']
headers: //response header
I don't know how to mock the correct response with the correct api call in the test cases. Is there any way that I can mock the correct response with the API calls?
Basic usage.
import * as axios from "axios";
// Mock out all top level functions, such as get, put, delete and post:
jest.mock("axios");
// ...
test("good response", () => {
axios.get.mockImplementation(() => Promise.resolve({ data: {...} }));
// ...
});
test("bad response", () => {
axios.get.mockImplementation(() => Promise.reject({ ... }));
// ...
});
With response code.
axios.get.mockImplementation(() => Promise.resolve({ status: 200, data: {...} }));
Based on parameters.
axios.get.mockImplementation((url) => {
if (url === 'www.example.com') {
return Promise.resolve({ data: {...} });
} else {
//...
}
});
Try something like this:
axiosPostSpy = jest.spyOn(axios, 'post').mockImplementation((url) => {
if( url === 'www.test.com?key=serverKey') {
return {
data: {
success: false,
},
};
} else {
return {
data: {
success: true,
},
};
}
});

Fetch request always returns network error

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.

Resources