React Query handling response status codes - reactjs

this is related to this question:
Handling unauthorized request in react-query
I understand the point that React-Query doesnt care about responses codes because there is no error. So for example if the server respond with a 400 "Bad Request", do i have to check for this on the data returned by the muate function?
const handleFormSubmit = async (credentials) => {
const data = await mutateLogin(credentials);
// Do i have to check this data if for example i wanna show an error message
// "Invalid Credentials"?
};
I need to save the user on the cache.
const useMutateLogin = () => {
return useMutation(doLogin, {
throwOnError: true,
onSuccess: data => // Do i have to check here again if i receive the user or 400 code
})
}
Thanks.

react-query does not take care of the requests and it is completely agnostic of what you use to make them as long you have a Promise. From the documentation we have the following specification for the query function:
Must return a promise that will either resolves data or throws an error.
So if you need to fail on specific status codes, you should handle that in the query function.
The confusion comes because popular libraries usually take care of that for you. For example, axios and jQuery.ajax() will throw an error/reject if the HTTP status code falls out of the range of 2xx. If you use the Fetch API (like the discussion in the link you posted), the API won't reject on HTTP error status.
Your first code snippet:
const handleFormSubmit = async (credentials) => {
const data = await mutateLogin(credentials);
};
The content of data depends on the mutateLogin function implementation. If you are using axios, the promise will reject to any HTTP status code that falls out of the range of 2xx. If you use the Fetch API you need to check the status and throw the error or react-query will cache the whole response as received.
Your second code snippet:
const useMutateLogin = () => {
return useMutation(doLogin, {
throwOnError: true,
onSuccess: data => // Do i have to check here again if i receive the user or 400 code
})
}
Here we have the same case as before. It depends on doLogin implementation.

Related

Handling errors conditionally in react-query

I started using react-query a couple of days ago and everything seems amazing but I don't understand how I can handle errors returned from the server with their status code and error key
lets take the peace of code below as an example
const onError = (error) => {
console.log('error occurred', error)
}
let { id } = useParams()
const { data: User, isLoading, isError, error, isRefetching, status, refetch } = useQuery(['get-user-by-id', id], getUserById(id), {
onError
})
in this scenario when the API returns an error the onError function does not fire furthermore when i try to render a toast containing the {error} the message is Missing queryFn and when rendering {error?.message} the message is just Error
i would like to be able to get the message sent from the server with its key, ex.
if(error?.response.status === 404){
if(error?.response?.data?.detail){
let error = error?.response?.data?.detail
}else if(error?.response?.data?.message){ //another error key that might return
let error = error?.response?.data?.message
}// and so on...
}else if (error?.response?.status === 400){} // and so on...
or a key that I know my API would return depending on the status code this is especially critical for forms, while a get request could be fine with simple non-detailed error messages, a post request might hold relevant information about the error that can help the user understand it like if a name for a certain field is already taken or their is complex validation involved in the serverside, how can i handle error's in the way i explained above?
Your problem is, according to your code, that you are not passing a function as the query function parameter, but you are executing the function during render:
useQuery(['get-user-by-id', id], getUserById(id),
unless this was a typo, what you need is:
useQuery(['get-user-by-id', id], () => getUserById(id),
but given that you said you get Missing queryFn as error, I'm pretty sure this is it.

how intercept and stub the response of a rpc call in react with cypress

I want to intercept a rpc call that I made to the api in my react app. I'm using a custom hook that receives the buffer and the rpc method that I want to call and returns the data(something like react-query useQuery hook).
The thing is because of being a rpc call, the request urls of my requests are all the same and the response is binary, I can't distinguish the requests and intercept the one to stub.
One example of making a rpc call:
const {response, loading, error} = useRpc({
Buffer: GetUser,
Request: GetUserRequest
});
Edit 1:
I'm using
cy.fixture('fixutre-file').then((data) => {
const response = new TextDecoder().decode(res.body);
cy.intercept('https://example.com/', { method: 'POST' },
(req) => {
req.continue((res) => {
if ("some condition for distinguishing the request I want to intercept, here") {
res.send({ fixture: 'fixutre-file' });
}
});
});
}):
to get the response and decide whether or not intercept this req and instead send back my fixture data. But the response constant is still some unreadable string. What's wrong with my approach?
Edit 2:
Another approach that I used, was to use the cypress-protobuf package and encode my fixture.json file with the related protobuffer file:
cy.fixture('fixutre-file').then((data) => {
cy.task('protobufEncode', {
fixtureBody: data,
message: 'the_message',
protoFilePath: './protobuf/protofile.proto'
}).then((encodedData) => {
cy.intercept('https://example.com/', { method: 'POST' },
(req) => {
/////// approach 1(didn't work): ///////
// can't use this approach(because there is no identifier on
// req object to distinguish the requests I want to
// intercept)
// if ("some condition based on `req` here") {
// req.reply(encodedData);
// } else {
// req.continue();
// }
/////// approach 2: ///////
// using encodedData to compare it with res.body
req.continue(res => {
// can't compare res.body with encodedData, because
// encodedData is an empty string!
});
}).as('needToWait');
cy.wait('#needToWait').get('some selector').should('exist')
});
}):
Now the problem is:
encodedData is just an empty string, meaning it didn't work, so I can't compare the response with my fixture data to intercept the related request
You can simply check for some value from the request that distinguishes it from the other requests. Request bodies and headers are often good places to start. Additionally, you can use req.alias to conditionally assign an alias if you need to wait for that specific call.
cy.intercept('/foo', (req) => {
if (req.body.bar === true) { // or whatever logic indicates the call you want to intercept
req.alias = 'baz'; // conditionally assign alias
req.reply({foo: 'bar'}); // mock response
} else {
req.continue(); // do not mock response
}
});
cy.get('something')
.click()
.wait('#baz'); // waits for your specific 'baz' call to happen.

How to cancel subsequent requests in axios interceptor?

I am working on a project in react with redux/redux-saga and a doubt arose. I am implementing with Axios in the response interceptor a way to logout the user when the session token has expired.
Basically, what I'm looking for is, to logout the user when calling to a private endpoint and it returns a 403. But I have this problem:
I have routes where I must do 3 dispatches (calls to different endpoints on the API) during the component loads which all 3 bring me relevant information to the components. Obviously, when the token is expired it will return 403, and the interceptor in the response will do the logout process to remove it from the session. However, even after doing the logout, the other 2 requests will also be called and there is no need cuz I already detected in the first call that the token expired.
// EFFECTS
useEffect(() => {
dispatch(getAccountsInit("users"));
dispatch(getAccountsInit("kash"));
dispatch(getBanksInit());
dispatch(getCurenciesInit());
}, [dispatch]);
How do I prevent this? How do I cancel subsequent requests when detecting that the token has expired on the first one? .. I was looking for information about it but I couldn't find it. I thank you very much for the help.
Here is my response inteceptor
export const resInterceptor = (instance) =>
instance.interceptors.response.use(
(res) => res,
(error) => {
const configRequest = error.config,
status = error.status || error.response.status;
console.warn("Error status: ", status || error.code);
console.log(error);
if (status === 418 && !configRequest._retry) {
alert("Ha finalizado tu sesión, serás re dirigido y deberás iniciar sesión nuevamente.");
store.dispatch(logoutSuccess());
}
I assume action creators like getAccountsInit are async thunks. In this meaning, you actually send 4 requests in parallel and once any of it gets 403, there is no use try stopping others. This way you can only try preventing of interceptor to call logoutSuccess() but is it really needed? I doubt.
You can refer to store inside interceptor in order to get current "logged in or not" status, but to me it seems as unnecessary complication.
You also can return Promise from your getAccountsInit, getAccountsInit etc and chain them:
useEffect(() => {
dispatch(getAccountsInit("users"))
.then(() => dispatch(getAccountsInit("kash")))
.then(() => dispatch(getBanksInit()))
.then(() => dispatch(getCurenciesInit()));
}, [dispatch]);
But this way for "normal" flow(session has not expired) user will get ~4x longer loading(instead of running in parallel requests go in sequence).
TL;DR; just let it sending requests even if they may be useless due to session expiration; code complexity or slower loading is not worth it

What's the best way to store a HTTP response in Ionic React?

I'm developing an app with Ionic React, which performs some HTTP requests to an API. The problem is I need to store the response of the request in a local storage so that it is accessible everywhere. The way I'm currently doing it uses #ionic/storage:
let body = {
username: username,
password: password
};
sendRequest('POST', '/login', "userValid", body);
let response = await get("userValid");
if (response.success) {
window.location.href = "/main_tabs";
} else if (!response.success) {
alert("Incorrect password");
}
import { set } from './storage';
// Handles all API requests
export function sendRequest(type: 'GET' | 'POST', route: string, storageKey: string, body?: any) {
let request = new XMLHttpRequest();
let payload = JSON.stringify(body);
let url = `http://localhost:8001${route}`;
request.open(type, url);
request.send(payload);
request.onreadystatechange = () => {
if (request.readyState === 4 && storageKey) {
set(storageKey, request.response);
}
}
}
The problem is that when I get the userValid key the response hasn't come back yet, so even awaiting will return undefined. Because of this I have to send another identical request each time in order for Ionic to read the correct value, which is actually the response from the first request. Is there a correct way of doing this other than just setting timeouts everytime I perform a request?
You are checking for the results of storage before it was set. This is because your sendRequest method is calling an asynchronous XMLHttpRequest request, and you are checking storage before the sendRequest method is complete. This can be fixed by making sendRequest async and restructuring your code a bit.
I would suggest you instead look for examples of ionic react using hooks or an API library - like fetch or Axios. This will make your life much easier, and you should find lots of examples and documentation. Check out some references below to get started:
Example from the Ionic Blog using Hooks
Example using Fetch using React
Related Stack Overflow leveraging Axios

What did I wrong in the code. I am getting late response from Axios

I am trying to validate user details in submit, calling this method from submit click
validateCredentials() {
console.log('Validateuser')
let isUserValid = false
axios.get('http://localhost:12345/api/Login/ValidateCredentials?UserID=abc&Password=abc')
.then(res=>{isUserValid = response.data.Item1})
console.log(isUserValid)
return isUserValid
}
Here isUserValid is always true from API, but first time it returns false. After login Failed getting response as True
and the output
Output console log
If you want to use the server response synchronously and assign to a variable you need to use promises or async/await
In the above code you are using a asynchronous code axios call to set the value, but will not work all the time
Use the below code to perform asynchronous task synchronously
async validateCredentials() {
console.log('Validateuser')
let isUserValid = false
let reponse = await axios.get('http://localhost:12345/api/Login/ValidateCredentials?UserID=abc&Password=abc')
isUserValid = respose.data.Item1
console.log(isUserValid)
return isUserValid
}
Also when calling the function validateCredentials use await or promise to wait till the validation is set successfully

Resources