Closed anthonyss09 closed 2 weeks ago
If you're going to file an issue, you need to actually give some meaningful description of what's happening, and provide examples that reproduce the problem. We can't do anything with this little info.
I created this by accident, it's my first time creating an issue and I was figuring out the formatting. Should I edit this issue with all the information or start a new one?
Ah, okay. Fill this issue and leave another comment when you're done.
Thank you. I hope i've provided all the necessary information.
What's the actual error shown by Next? Can you include a screenshot of that?
It depends on the error I receive from server. Here is one specific response where email & password are incorrect.
So it doesn't break my code, but i still get the error alert like so...
And because It still says unhandled upon inspection, I assume this is not the expected behavior.
Hmm. Not sure what about this qualifies as "unhandled", or what's triggering Next's logic specifically here.
Here is the error object in the console. It has a property isUnhandledError and it remains true. Just for kicks I set it to false in the catch block but at that point maybe next has already read this property. Maybe the error object sent back from shopify storefront api is unfamiliar to rtk and the isUnhandledError is not being set or read properly. That's the only thought i'm left with right now.
Oh, wait. That's probably because you're doing this in the transformResponse
option:
transformResponse: (response: any) => {
if (response.customerAccessTokenCreate?.customerUserErrors.length > 0) {
throw new Error(
response.customerAccessTokenCreate.customerUserErrors[0].message
);
}
},
that would definitely make this "unhandled". transformResponse
shouldn't be throwing errors.
I do that because shopify will return a response with customUserErrors array that doesn't throw or register as an error. So in the instance of invalid credentials I will receive a success response which I can interogate and throw a client side error when calling the hook, but I wanted it all in the same place so I throw the error in transform response. But other server errors such as 'too many attempts' will come back as error and the same flow with occur.
I just commented out the transform response and once I hit the limit on attempts the same issue occurs upon receiving the error.
I think I might be able to restructure my base query & properly rewrite my endpoint request. This link hopefully will fix me up, i'll report back.
https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#graphql-basequery
First of all you were right of course to mention the throw in transform response. I thought I would be catching it once queryFulfilled, but that throw is out of the range of the try/catch in onQueryStarted. So now I inspect the response for customerUserErrors in onQueryStarted and throw the error there if customerUserErrors has length.
For my uncaught error responses problem I found a stack question that lead to my solve. https://stackoverflow.com/questions/74101408/mismatch-of-types-for-rtk-query-graphql-request-and-graphql-request
I stopped creating graphQLClient from graphql-request package and just added the url to graphqlRequestBaseQuery. GraphQLClient from graphql-request was throwing an error I wasn't handling.
The updated files below:
apiSlice.ts
import { createApi } from "@reduxjs/toolkit/query/react";
import { graphqlRequestBaseQuery } from "@rtk-query/graphql-request-base-query";
export const apiSlice = createApi({
reducerPath: "api",
baseQuery: graphqlRequestBaseQuery({
url: `https://${process.env.NEXT_PUBLIC_SHOP_NAME}.myshopify.com/api/${process.env.NEXT_PUBLIC_VERSION}/graphql.json`,
prepareHeaders: (headers, { getState }) => {
headers.set("Accept", "application/json");
headers.set("Content-Type", "application/json");
headers.set(
"X-Shopify-Storefront-Access-Token",
process.env.NEXT_PUBLIC_SHOPIFY_ACCESS_TOKEN
? process.env.NEXT_PUBLIC_SHOPIFY_ACCESS_TOKEN
: ""
);
return headers;
},
}),
tagTypes: ["Cart", "Customer"],
endpoints: (build) => ({}),
});
authSlice.ts
import { createSlice } from "@reduxjs/toolkit";
import type { RootState } from "../../store";
import { apiSlice } from "../api/apiSlice";
import {
createTokenArgs,
} from "./types";
import { showAlert, clearAlert } from "../alerts/alertsSlice";
import { gql} from "graphql-request";
let customerAccessToken;
if (typeof localStorage !== "undefined") {
const localToken = localStorage.getItem("blissCustomerAccessToken");
if (localToken !== null) {
customerAccessToken = JSON.parse(localToken);
} else {
customerAccessToken = null;
}
} else {
customerAccessToken = null;
}
const initialState = {
customerAccessToken,
customer: { firstName: "", id: null },
};
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
setCustomerAccessToken(state, action) {
state.customerAccessToken = action.payload;
},
setCustomerData(state, action) {
state.customer = action.payload;
},
logoutCustomer(state, action) {
state.customer = { firstName: "", id: null };
state.customerAccessToken = null;
},
},
});
export const extendedApi = apiSlice.injectEndpoints({
endpoints: (build: any) => ({
createCustomerToken: build.mutation({
query: (arg: createTokenArgs) => ({
document: gql`
mutation {
customerAccessTokenCreate(input: {email: "${arg.email}", password: "${arg.password}"})
{
customerAccessToken {
accessToken
}
customerUserErrors {
message
}
}
}
`,
}),
invalidatesTags: ["Customer"],
async onQueryStarted(arg: any, lifecycleApi: any) {
try {
const response = await lifecycleApi.queryFulfilled;
if (
response.data.customerAccessTokenCreate.customerUserErrors.length
) {
throw new Error(
response.data.customerAccessTokenCreate.customerUserErrors[0].message
);
}
} catch (error: any) {
const errorMessage = error.error
? error.error.message
: error.message;
lifecycleApi.dispatch(
showAlert({
alertMessage: errorMessage,
alertType: "danger",
})
);
setTimeout(() => {
lifecycleApi.dispatch(clearAlert({}));
}, 2000);
console.log("some error occured creating access token", error);
}
},
}),
}),
});
export const selectAuthData = (state: RootState) => state.auth;
export const { setCustomerAccessToken, setCustomerData, logoutCustomer } =
authSlice.actions;
export const {
useCreateCustomerTokenMutation,
} = extendedApi;
export default authSlice.reducer;
I have apiSlice where I create basequery with qraphqlbasequery. In authSlice.ts I inject endpoints. At the endpoint createCustomerToken I use onQueryStarted where I await queryFulfilled in try/catch. The catch block catches server response errors and I display an alert to client which all works. The problem is I still see the nextjs red error circle on UI & the error is still logged as unhandled. I've added errorLogger.ts middleware to the store as an added precaution but I cannot seem to handle errors coming from server. Relevant code below. Thanks.
apiSlice.ts
authSlice.ts
errorLogger.ts
store.ts