Closed progtarek closed 7 months ago
There is always the chance that "some other error" happens, e.g. if you throw something in a queryFn
. That would end up being a SerializedError
.
Please take a look at type safe error handling
@phryneas I'm stuck for a few hours trying to figure out how to type the errors correctly. I've read the docs but I still don't quite get it. All my endpoints throw the same shape of error, such as below:
{
"message": "OTP not found or expired or invalid",
"errors": {
"otp": [
"OTP not found or expired or invalid"
]
}
}
interface ApiErrorResponse {
message: string;
errors: {[k: string]: string[]}
}
Are we supposed to follow what the OP did which is to cast the custom error interface at createApi
?
Or we have to cast it manually on every mutation?
const [verifyOtp, result] = useVerifyOtpMutation();
try {
await verifyOtp();
} catch (err) {
if ('data' in err) {
const customError = err as ApiErrorResponse; // Is this right? And we have to repeat this on every mutation?
console.log(customError.message);
}
}
@nubpro that looks like a very non-standard err
object you have there - is that actually in that shape on runtime? What code have you written for that?
@nubpro that looks like a very non-standard
err
object you have there - is that actually in that shape on runtime? What code have you written for that?
Our backend is actually written on Laravel. Hence, we are folllowing their standard of practise with their API.
On a side note, throwing custom errors using React Query + Axios
is quite straightfoward. Let me show you a contrived example of what I mean:
// note: this is a different type than the one I shown previously
type ServerResponse<T = void> = {
status: string;
message: string | null;
data?: T;
};
// Take a look below "AxiosError<ServerResponse>"
const { status, error, data } = useQuery<ServerResponse<DataReferences>, AxiosError<ServerResponse>>(
['getConsultantFormReferences'],
() => axiosInstance.get('/api/consultant/getReferences').then(res => res.data)
);
As you can see, I can easily wrap their useQuery
with a custom error type to align with my expected server error's response. Hence, I'm wondering if the same can be done on RTK Query?
@nubpro all that assumes that an error is "working" though.
But that's against the nature of an error. An error is something unexpected.
What if a reverse proxy somewhere along the line is not working and injects its own content? What if the virus scanner on the user's computer changes something? What if the server is misconfigured and returns only garbled content?
And then, the more obvious ones: what if the user's internet is down, the connection is interrupted, or something along those lines? In those cases, you don't even have data
to work with.
Simply casting error.data
to have a certain value will be just wrong in those cases.
There is a reason why an error in a catch
block in TypeScript is always unknown
.
And we go for a similarly defensive approach: An error is (if you are using fetchBaseQuery
) either a FetchBaseQueryError
(if the error is kinda known and could be handled correctly if the baseQuery
) or a SerializedError
(if something completely unexpected happened, like your query
function crashing). At that point, realistically you can still not make any assumptions on the shape of error.data
- you have to test if the shape what it is, for example with a zod
schema.
So I would write a zod
schema for "a FetchBaseQueryError with a certain type of data
attached" and use that in your component. It's the only safe way to know that the error is really what you expect it to be.
@nubpro all that assumes that an error is "working" though.
But that's against the nature of an error. An error is something unexpected. What if a reverse proxy somewhere along the line is not working and injects its own content? What if the virus scanner on the user's computer changes something? What if the server is misconfigured and returns only garbled content? And then, the more obvious ones: what if the user's internet is down, the connection is interrupted, or something along those lines? In those cases, you don't even have
data
to work with.Simply casting
error.data
to have a certain value will be just wrong in those cases. There is a reason why an error in acatch
block in TypeScript is alwaysunknown
. And we go for a similarly defensive approach: An error is (if you are usingfetchBaseQuery
) either aFetchBaseQueryError
(if the error is kinda known and could be handled correctly if thebaseQuery
) or aSerializedError
(if something completely unexpected happened, like yourquery
function crashing). At that point, realistically you can still not make any assumptions on the shape oferror.data
- you have to test if the shape what it is, for example with azod
schema.So I would write a
zod
schema for "a FetchBaseQueryError with a certain type ofdata
attached" and use that in your component. It's the only safe way to know that the error is really what you expect it to be.
Thanks for the detail insight. With that said, I have created a helper that would determine whether the exception error is coming from the server (I called it ApiResponse
) or fetchBaseQueryError
or unknown
as you have said.
By simply checking whether the status
returned is a number or not, if it is, I'm sure that it is from the server.
Helpers:
export interface ApiErrorResponse {
status: number;
data: { message: string; errors: { [k: string]: string[] } };
}
export function isApiResponse(error: unknown): error is ApiErrorResponse {
return (
typeof error === "object" &&
error != null &&
"status" in error &&
typeof (error as any).status === "number"
);
}
In practise:
try {
await sendOtp().unwrap();
} catch (error) {
if (isApiResponse(error)) {
// present error to the user
alert(error.data.message)
} else {
// log error
console.error(error);
}
}
What do you think of this way?
Yes, that is very close to this example from the docs
Yup, I did reference the code there and modified to suit my needs. Thanks again, Lenz, you were a big help!
From: Lenz Weber-Tronic @.> Sent: Friday, December 2, 2022 3:10:12 AM To: reduxjs/redux-toolkit @.> Cc: Chai @.>; Mention @.> Subject: Re: [reduxjs/redux-toolkit] How to customize error type for queries and mutations? (Issue #2942)
Yes, that is very close to this example from the docshttps://redux-toolkit.js.org/rtk-query/usage-with-typescript#inline-error-handling-example
— Reply to this email directly, view it on GitHubhttps://github.com/reduxjs/redux-toolkit/issues/2942#issuecomment-1334226095, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AAF2IIRSZ6ZHUBTG5FJ7LTLWLDZZJANCNFSM6AAAAAASKJROEM. You are receiving this because you were mentioned.Message ID: @.***>
Versions used
I am trying to add general type support for the incoming (server side) errors After investigating I found this link https://github.com/rtk-incubator/rtk-query/issues/86
As I understood here https://github.com/rtk-incubator/rtk-query/issues/86#issuecomment-738845312 this is something essentially dictated by the fetchQuery
and this is my implementation so far
Although it shows that the incoming type is
const [createUser, { error, isError, isLoading }] = useCreateUserMutation()
const error: SerializedRemoteError | SerializedError | undefined
trying to access a key inside the customized type giving me this error