-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Fix(Admin UI): Display error message when default tax zone is missing #3999
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Fix(Admin UI): Display error message when default tax zone is missing #3999
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughModified error handling in the CreateProductVariantsDialog component to capture and display error messages via toast notifications. Added import for the Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10–15 minutes
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Tip 📝 Customizable high-level summaries are now available in beta!You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.
Example instruction:
Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx(3 hunks)
🔇 Additional comments (2)
packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx (2)
24-24: LGTM! Toast import added correctly.The
sonnerlibrary is appropriate for displaying user-facing error notifications.
184-186: LGTM! Loading state logic is consistent.The loading indicator correctly checks all three mutations (
createOptionGroupMutation,addOptionGroupToProductMutation, andcreateProductVariantsMutation), matching the disabled state logic above.
| const fieldErrors = (error as any)?.fieldErrors; | ||
| const errorMessage = | ||
| fieldErrors?.[0]?.message ?? | ||
| 'An unexpected error occurred while creating variants.'; | ||
|
|
||
| toast.error(errorMessage); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Script executed:
#!/bin/bash
# Search for error handling patterns in GraphQL mutations
echo "=== Searching for useMutation error handling patterns ==="
rg -n -A8 "useMutation" --type=tsx --type=ts -g "!**/node_modules/**" | grep -A8 "catch\|error" | head -80
echo -e "\n=== Searching for fieldErrors usage patterns ==="
rg -n -C4 "fieldErrors" --type=tsx --type=ts -g "!**/node_modules/**"
echo -e "\n=== Searching for error handling in similar dialogs/components ==="
rg -n -B5 -A10 "toast\.error" --type=tsx --type=ts -g "!**/node_modules/**" | head -100Repository: vendure-ecommerce/vendure
<shell_result>
Repository: vendure-ecommerce/vendure
Script execution failed with unrecoverable error: [CAPABILITY:RUN_SHELL_SCRIPT] Failed to clone repository vendure-ecommerce/vendure: Clone operation failed
</shell_result>
Script executed:
#!/bin/bash
# Get context from the file under review
echo "=== Reading the complete error handling section from the file ==="
FILE="packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx"
if [ -f "$FILE" ]; then
wc -l "$FILE"
echo -e "\n--- Lines 120-150 context ---"
sed -n '120,150p' "$FILE"
else
echo "File not found at $FILE"
fiRepository: vendure-ecommerce/vendure
<shell_result>
Repository: vendure-ecommerce/vendure
Script execution failed with unrecoverable error: [CAPABILITY:RUN_SHELL_SCRIPT] Failed to clone repository vendure-ecommerce/vendure: Clone operation failed
</shell_result>
GraphQL error handling fieldErrors vs graphQLErrors structure best practices
<web_search_result>
Short answer — follow "errors-as-data" for user/field validation and keep GraphQL's errors array for system/exceptional failures. Key points and best practices:
-
Separate concerns
- Use graphQLErrors (and networkError) for unexpected/system errors (exceptions, infra, parsing). Let these populate error.graphQLErrors on clients. [1][2]
- Encode predictable, user-facing failures (validation, business rules) in the schema as typed error objects or as a structured fieldErrors payload inside data so clients can handle them deterministically. (“Errors as data” pattern). [3][4]
-
Prefer schema-typed errors for recoverable/business errors
- Return union/explicit error types or an errors field inside the operation/type (e.g. Result = Success | ValidationFailure) so the contract is typed and discoverable. [3][9]
-
Standardize shape and codes
- If you must use the errors array, include stable machine codes and structured metadata in extensions (code, httpStatus, traceId, fieldErrors map). For field-level problems include field names and messages. Example extension: { code: "VALIDATION_FAILED", fieldErrors: { email: "invalid", password: "too_short" } }. [4][3]
-
Preserve partial success and use client errorPolicy
- Design resolvers to allow partial data when possible. Configure client errorPolicy (none/ignore/all) to control whether graphQLErrors block data. [2][1]
-
Centralize mapping & logging
- Map server exceptions to either: (a) errors array with extensions for unexpected failures, or (b) schema error objects for expected failures. Log/tracing for system errors; keep user errors concise. [1][4]
-
Practical checklist
- Define a small enum of error codes (UNAUTHENTICATED, FORBIDDEN, NOT_FOUND, VALIDATION_FAILED…). [4]
- Put field-level validation detail in the data payload or extensions, not as free-form message text. [3][4]
- Use unions/interfaces in the schema for typed error responses when clients need to branch on error types. [3][9]
- Ensure clients handle both graphQLErrors and field-errors-as-data (and respect errorPolicy). [2][1]
Selected references
- Apollo: graphQLErrors vs networkError, errorPolicy (client handling). [1][2]
- Apollo: Errors-as-data (use schema to encode recoverable errors). [3]
- Practical patterns & standardization guidance. [4]
</web_search_result>
Apollo Client mutation error structure error.graphQLErrors error.fieldErrors
<web_search_result>
Short answer
- Apollo Client mutation errors come back as an ApolloError object with these main fields: graphQLErrors (array of GraphQLError objects from the server), networkError, and message. The GraphQLError objects include message, path, locations and an extensions object that servers use to send extra details. [1]
Why you may see error.fieldErrors
- error.fieldErrors is not a built‑in Apollo Client property. It's a common pattern where the server places per‑field validation info inside the GraphQLError.extensions (for example extensions.fieldErrors or extensions.exception.fieldErrors). The client then reads those extensions from error.graphQLErrors. [1][2]
How to produce fieldErrors on the server (example)
- Throw a GraphQLError / ApolloError with custom extensions:
const { GraphQLError } = require('graphql');
throw new GraphQLError('Validation failed', {
extensions: { code: 'BAD_USER_INPUT', fieldErrors: { email: 'Invalid email', password: 'Too short' } }
});
This results in errors[*].extensions.fieldErrors arriving to the client. [1][2]
How to read them on the client (example)
- Using Apollo Client mutation try/catch:
try {
await client.mutate({ mutation: MY_MUTATION, variables });
} catch (err) {
// err is an ApolloError
err.graphQLErrors.forEach(gqlErr => {
const fieldErrors = gqlErr.extensions?.fieldErrors;
// fieldErrors might be an object like { email: 'Invalid' }
});
}
Notes
- Exact location/name (fieldErrors, validationErrors, errors) is defined by your server. Always inspect err.graphQLErrors and gqlErr.extensions to find the shape your backend sends. [1][2]
Sources
- Apollo Server — Error handling (GraphQLError extensions, custom errors). [1]
- Apollo Server docs: creating custom errors / ApolloError example (using extensions object for custom fields). [2]
</web_search_result>
Verify the error handling uses the correct Apollo Client error structure.
The code accesses (error as any)?.fieldErrors directly on the error object, which does not match standard Apollo Client error patterns. In Apollo Client mutations, GraphQL errors arrive in error.graphQLErrors array, and field-level validation details are typically nested in error.graphQLErrors[0].extensions?.fieldErrors (if the backend places them there), not directly on the error object.
Using the incorrect access pattern means:
- The tax zone error from the backend won't be captured if it's in the standard location
- Users will consistently see the generic "An unexpected error occurred" message instead of the actual error
- The type assertion
(error as any)masks the structural mismatch
The correct pattern should inspect error.graphQLErrors and check the extensions object:
- const fieldErrors = (error as any)?.fieldErrors;
- const errorMessage =
- fieldErrors?.[0]?.message ??
- 'An unexpected error occurred while creating variants.';
-
- toast.error(errorMessage);
+ let errorMessage = 'An unexpected error occurred while creating variants.';
+
+ if (error && typeof error === 'object') {
+ const graphQLError = (error as any).graphQLErrors?.[0];
+ const fieldErrors = graphQLError?.extensions?.fieldErrors;
+
+ errorMessage =
+ (Array.isArray(fieldErrors) && fieldErrors[0]?.message) ??
+ (typeof fieldErrors === 'object' && Object.values(fieldErrors)[0]) ??
+ graphQLError?.message ??
+ (error as any).message ??
+ errorMessage;
+ }
+
+ toast.error(errorMessage);Verify with the backend team which error structure is actually sent and update accordingly.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const fieldErrors = (error as any)?.fieldErrors; | |
| const errorMessage = | |
| fieldErrors?.[0]?.message ?? | |
| 'An unexpected error occurred while creating variants.'; | |
| toast.error(errorMessage); | |
| let errorMessage = 'An unexpected error occurred while creating variants.'; | |
| if (error && typeof error === 'object') { | |
| const graphQLError = (error as any).graphQLErrors?.[0]; | |
| const fieldErrors = graphQLError?.extensions?.fieldErrors; | |
| errorMessage = | |
| (Array.isArray(fieldErrors) && fieldErrors[0]?.message) ?? | |
| (typeof fieldErrors === 'object' && Object.values(fieldErrors)[0]) ?? | |
| graphQLError?.message ?? | |
| (error as any).message ?? | |
| errorMessage; | |
| } | |
| toast.error(errorMessage); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error object here comes from awesome-graphql-client, not Apollo.
This client wraps the GraphQL errors array into a fieldErrors property on the thrown error.
There is no graphQLErrors field in this context, which is why accessing it returned undefined during testing.
I’ve kept the fieldErrors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for awesome-graphql-client import and usage patterns in the codebase
echo "=== Searching for awesome-graphql-client imports ==="
rg -n "awesome-graphql-client" --type=ts --type=tsx -C2
echo -e "\n=== Searching for other error handling patterns with fieldErrors ==="
rg -n "fieldErrors" --type=ts --type=tsx -C3
echo -e "\n=== Checking the graphql api wrapper implementation ==="
fd -e ts -e tsx "api.ts" -x cat {}
Length of output: 48907
🌐 Web query:
awesome-graphql-client error structure fieldErrors
💡 Result:
Short answer: GraphQLRequestError.fieldErrors is an array of GraphQL-formatted error objects (GraphQLFieldError[]). Each entry corresponds to an item from the server's response.errors and uses the standard GraphQL error shape — at minimum message, and usually locations, path and extensions when the server provides them. See the package docs and the GraphQL error format. [1][2]
Sources
- awesome-graphql-client (API / GraphQLRequestError docs). [1]
- GraphQL error/response format (message, locations, path, extensions). [2]
@PavanendraBaahubali, thank you for the clarification! You're absolutely right. I apologize for the confusion in my previous comment.
I've confirmed that awesome-graphql-client throws errors with a fieldErrors property that contains an array of GraphQL error objects (each with message, locations, path, and extensions). Your implementation is correct for this client:
const fieldErrors = (error as any)?.fieldErrors;
const errorMessage =
fieldErrors?.[0]?.message ??
'An unexpected error occurred while creating variants.';This properly extracts the error message from the first field error and falls back to a generic message if unavailable. The error handling should now correctly display the "The active tax zone could not be determined" message to users when the defaultTaxZone is NULL.
🧬 Code Graph Analysis Results
- File: packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx
-
Snippet 1: Error handling block that extracts fieldErrors from the thrown error and reports via toast
-
Content:
async function handleCreateVariants() {
...
} catch (error) {
console.error('Error creating variants:', error);const fieldErrors = (error as any)?.fieldErrors; const errorMessage = fieldErrors?.[0]?.message ?? 'An unexpected error occurred while creating variants.'; toast.error(errorMessage);}
-
Notes: This directly aligns with the user comment about fieldErrors being the wrapped error structure from awesome-graphql-client rather than graphQLErrors.
-
-
Snippet 2: Mutation usage context relevant to error handling (demonstrates how mutations are executed and where errors would surface)
- Content (excerpt around mutation usage):
const createOptionGroupMutation = useMutation({
mutationFn: api.mutate(createProductOptionGroupDocument),
});
const addOptionGroupToProductMutation = useMutation({
mutationFn: api.mutate(addOptionGroupToProductDocument),
});
const createProductVariantsMutation = useMutation({
mutationFn: api.mutate(createProductVariantsDocument),
}); - Purpose: Shows that mutations are executed via mutateAsync within the try/catch in handleCreateVariants, which is where fieldErrors handling occurs.
- Content (excerpt around mutation usage):
-
Snippet 3: Related GraphQL documents for context (optional for understanding shape of mutations that could produce fieldErrors)
- Content:
export const createProductOptionGroupDocument = graphql(mutation CreateOptionGroups($input: CreateProductOptionGroupInput!) { createProductOptionGroup(input: $input) { id name code options { id code name } } }); - And:
export const addOptionGroupToProductDocument = graphql(mutation AddOptionGroupToProduct($productId: ID!, $optionGroupId: ID!) { addOptionGroupToProduct(productId: $productId, optionGroupId: $optionGroupId) { id optionGroups { id code name options { id code name } } } }); - And:
export const createProductVariantsDocument = graphql(mutation CreateProductVariants($input: [CreateProductVariantInput!]!) { createProductVariants(input: $input) { id name } }); - Purpose: Helps understand the mutation surfaces that could throw errors with fieldErrors.
- Content:
-
Snippet 4: (If helpful) useMutation hook usage context from nearby code (for understanding how errors are surfaced via the hook)
- Content (from administrative UI snippet):
export function useMutation<T, V extends Record<string, any> = Record<string, any>>(
mutation: DocumentNode | TypedDocumentNode<T, V>,
) {
const { data, loading, error, runQuery } = useDataService<T, V>((dataService, variables) =>
dataService.mutate(mutation, variables),
);
const rest = { data, loading, error };
const execute = (variables?: V) => firstValueFrom(runQuery(variables));
return [execute, rest] as [typeof execute, typeof rest];
} - Note: Provides context that error is part of the returned rest object; in this file, errors are caught via try/catch around mutateAsync usage.
- Content (from administrative UI snippet):
-
✏️ Learnings added
Learnt from: PavanendraBaahubali
Repo: vendure-ecommerce/vendure PR: 3999
File: packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx:133-138
Timestamp: 2025-11-26T14:08:43.332Z
Learning: The Vendure dashboard codebase uses awesome-graphql-client (not Apollo Client) for GraphQL operations. The api wrapper is located at `packages/dashboard/src/vdb/graphql/api.ts`. When mutations throw errors, the error object contains a `fieldErrors` property (an array of GraphQL error objects), not `graphQLErrors`. Access error messages via `error.fieldErrors[0].message`.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.



Description
This PR resolves issue #3924
Bug Description
When creating a product variant, if the channel’s
defaultTaxZoneisNULL, the backend responds with:In the Admin UI, this error was only visible in the browser console.
The UI did not show any feedback, so the user had no idea why the variant wasn’t being saved.
What this PR changes
Breaking changes
No.
Screenshots
You can add screenshots here if applicable.
Checklist
📌 Always:
👍 Most of the time:
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.