probable-pink
probable-pink9mo ago

Handle Errors inside of Forms being posted to a custom API

Hey everyone! We're using Refine to such a great extend and I'm very thankful for all the work that has been put into Refine so far! Right now we're facing a situation, where we have a custom API that we are using to create workflows for users. Whenever we encounter errors inside the custom API, I'm wondering how to handle those respectively inside of the frontend. Here we have three different types of errors, that I'd like to handle. Maybe there is a solution to all three situations in Refine, but I'm missing a concept? 1. Handle global errors ✅ This one is pretty basic, and can be handled through a interceptor inside of Axios. Basically we encounter >500 errors inside of the API that might point to a network failure or general availability. I'm showing a toast right now if anything goes wrong on the server side. 2. Handle general form errors ❓ So this one is the first I'm struggling with, we basically check for general sanity of the form somebody posted. Since we're editing DTOs inside of our backend, say there is a check, that a certain combination of entries cannot be done, i.e. "This combination of attributes is not allowed". How would I handle such a case, what would the optimal JSON response from the API look like to scope the error to the form and show it inside of refine? I want to have a uniform solution, so that we don't have to build this again and again. Suggestions highly welcome! 3. Handle scoped form field errors ❓ This one is the second one I'm struggling with, since we have a collection of fields i.e. "id, name, zip, city", the server side could be checking something like "does the zip and the city match?" and if they don't match I want to show an error on the city field (just as an example) like "your city does not match the zip you entered". How would I pass the error to the form, so that (in our case) Antd would be able to work with that? Maybe it's too custom, but maybe that's something you figured out already 🙂 Cheers!
21 Replies
kapa.ai
kapa.ai9mo ago
Hi @bastianwegge. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Hello! Refine provides a way to handle all three types of errors you mentioned. Let's go through them one by one: 1. Handle global errors: As you mentioned, you can handle global errors using an interceptor in Axios. This is also mentioned in the Refine documentation (https://refine.dev/docs/api-reference/core/providers/data-provider/). You can create a custom error with the HttpError type from Refine and reject the promise with this error. 2. Handle general form errors: Refine supports server-side validation out-of-the-box for all supported UI frameworks. To handle server-side validation errors, you need to return a rejected promise with the errors field from the dataProvider. The errors field can be a string, string[], boolean, or an object with key and message fields. If the field is an object, the key field will be used as a translation key. If the key is not found in the translation, the message field will be displayed. Here is an example of how to structure the error:
const error: HttpError = {
message: "An error occurred while updating the record.",
statusCode: 400,
errors: {
title: ["Title is required"],
content: {
key: "form.error.content",
message: "Content is required.",
},
tags: true,
},
};
const error: HttpError = {
message: "An error occurred while updating the record.",
statusCode: 400,
errors: {
title: ["Title is required"],
content: {
key: "form.error.content",
message: "Content is required.",
},
tags: true,
},
};
You can find more information about this in the Refine documentation (https://refine.dev/docs/advanced-tutorials/forms/server-side-form-validation/). 3. Handle scoped form field errors: The errors field in the HttpError type can be used to assign errors to specific form fields. When the dataProvider returns a rejected promise with the errors field, the useForm hook will assign the errors to the respective form fields. Here is an example of how to structure the error:
const error: HttpError = {
message: "An error occurred while updating the record.",
statusCode: 400,
errors: {
zip: ["Zip and city do not match"],
},
};
const error: HttpError = {
message: "An error occurred while updating the record.",
statusCode: 400,
errors: {
zip: ["Zip and city do not match"],
},
};
In this case, the error message "Zip and city do not match" will be displayed in the zip field. You can find more information about this in the Refine documentation (https://refine.dev/docs/advanced-tutorials/forms/server-side-form-validation/). I hope this helps! Let me know if you have any other questions. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai9mo ago
<@317433576721219584> kapa.ai is still learning and improving, please let us know how it did by reacting below
probable-pink
probable-pink9mo ago
In point 2 you were mentioning the errors field inside of the HttpError. But I don't see something like a general "form error". Say you want to tell the user that "saving didn't work" but you don't want to show a toast that hides after X seconds but rather something like a permanent message on top of the form. Is there something like this in Refine? @kapa.ai can you take another shot at the question I asked?
kapa.ai
kapa.ai9mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Hello! I'm glad to hear that you're finding Refine useful! Let's address your questions about error handling: 1. Handle global errors: You're correct that Axios interceptors can be used for global error handling. Refine also provides the onError method in the authProvider for handling error responses from the API. You can create your own business logic to handle the error such as refreshing the token, logging out the user, etc. Here's the relevant documentation (https://refine.dev/docs/api-reference/core/providers/auth-provider/). 2. Handle general form errors: For handling general form errors, Refine supports server-side validation out-of-the-box. You need to return a rejected promise with the errors field from the dataProvider. The errors field can be a string, string**], boolean, or an object with key and message fields. If the field is an object, the key field will be used as a translation key. If the key is not found in the translation, the message field will be displayed. [Here's the relevant documentation (https://refine.dev/docs/advanced-tutorials/forms/server-side-form-validation/). 3. Handle scoped form field errors: For handling scoped form field errors, you can use the same errors field in the HttpError type. When the dataProvider returns a rejected promise with the errors field, the useForm hook will assign the errors to the respective form fields. For example, if you have an error related to the city field, you can structure your error like this:
const error: HttpError = {
message: "An error occurred while updating the record.",
statusCode: 400,
errors: {
city: ["Your city does not match the zip you entered"],
},
};
const error: HttpError = {
message: "An error occurred while updating the record.",
statusCode: 400,
errors: {
city: ["Your city does not match the zip you entered"],
},
};
In this case, the error message "Your city does not match the zip you entered" will be displayed in the city field.
Here's the relevant documentation** (https://refine.dev/docs/advanced-tutorials/forms/server-side-form-validation/). I hope this helps! Let me know if you have any other questions. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai9mo ago
<@317433576721219584> kapa.ai is still learning and improving, please let us know how it did by reacting below
probable-pink
probable-pink9mo ago
Oh this is so great! Works like a charm @core you guys are awesome!!!!!
exotic-emerald
exotic-emerald9mo ago
hello @bastianwegge. thanks 🥲 also thanks to kappa ai 😀
probable-pink
probable-pink9mo ago
@kapa.ai Is there a way to use a custom field or custom ErrorList to show custom errors above the fields of the form? I want to show general errors for the form.
kapa.ai
kapa.ai9mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Yes, you can handle general form errors in Refine by using the errors field in the HttpError type. When the dataProvider returns a rejected promise with the errors field, the useForm hook will assign the errors to the respective form fields. For example, if you have a general error for the form, you can structure your error like this:
const error: HttpError = {
message: "An error occurred while updating the record.",
statusCode: 400,
errors: {
form: ["This combination of attributes is not allowed"],
},
};
const error: HttpError = {
message: "An error occurred while updating the record.",
statusCode: 400,
errors: {
form: ["This combination of attributes is not allowed"],
},
};
In this case, the error message "This combination of attributes is not allowed" will be displayed for the form. However, displaying this error message above the form fields would require custom implementation. You would need to access the errors state from the useForm hook and display the error message in your desired location. Here is an example of how you can access the errors state:
import { useForm } from "@refinedev/core";

const form = useForm({
// ...
});

// you can access the errors state from the useForm hook
console.log(form.mutationResult.error?.errors);
import { useForm } from "@refinedev/core";

const form = useForm({
// ...
});

// you can access the errors state from the useForm hook
console.log(form.mutationResult.error?.errors);
You can find more information about this in the Refine documentation (https://refine.dev/docs/advanced-tutorials/forms/server-side-form-validation/). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai9mo ago
<@317433576721219584> kapa.ai is still learning and improving, please let us know how it did by reacting below
probable-pink
probable-pink9mo ago
@kapa.ai form.mutationResult does not exist
kapa.ai
kapa.ai9mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- I apologize for the confusion. You're correct, the form.mutationResult does not exist. The correct way to access the mutationResult is directly from the useForm hook. Here's how you can do it:
import { useForm } from "@refinedev/core";

const { mutationResult } = useForm({
// ...
});

// you can access the errors state from the mutationResult
console.log(mutationResult.error?.errors);
import { useForm } from "@refinedev/core";

const { mutationResult } = useForm({
// ...
});

// you can access the errors state from the mutationResult
console.log(mutationResult.error?.errors);
In this case, mutationResult.error?.errors will give you the errors returned from the dataProvider. You can then display these errors above your form fields as per your requirements. You can find more information about this in the Refine documentation (https://refine.dev/docs/api-reference/core/hooks/useForm/). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai9mo ago
<@317433576721219584> kapa.ai is still learning and improving, please let us know how it did by reacting below
probable-pink
probable-pink9mo ago
@alicanerdurmaz is there maybe a nicer way to access a specific Error from the map of errors to show above my fields in my form?
exotic-emerald
exotic-emerald9mo ago
What exactly do you want to do, can you explain me more please?
probable-pink
probable-pink9mo ago
Yes of course! Say I have a form like this:
export const EditMyResource = () => {
const { t } = useTranslation();
const { id } = useParsed();
const { formProps } = RefineAntd.useForm<FormDataShape>({
resource: 'my-resource',
id,
});

return (
<Form
{...formProps}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
>
<Row gutter={[16, 16]}>
<Col span={12}>
<Card type="inner" title={t('my-resource.header')} extra={<ReadOutlined />}>
<Form.Item name="my-special-error">
<Form.ErrorList errors={errors} />
</Form.Item>
<Form.Item
label={t('my-resource.name')}
name={combinedPath('name', namePathPrefix)}
rules={validationMap.get('name')}
>
<Input />
</Form.Item>
</Card>
</Col>
</Row>
</Form>
)
}
export const EditMyResource = () => {
const { t } = useTranslation();
const { id } = useParsed();
const { formProps } = RefineAntd.useForm<FormDataShape>({
resource: 'my-resource',
id,
});

return (
<Form
{...formProps}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
>
<Row gutter={[16, 16]}>
<Col span={12}>
<Card type="inner" title={t('my-resource.header')} extra={<ReadOutlined />}>
<Form.Item name="my-special-error">
<Form.ErrorList errors={errors} />
</Form.Item>
<Form.Item
label={t('my-resource.name')}
name={combinedPath('name', namePathPrefix)}
rules={validationMap.get('name')}
>
<Input />
</Form.Item>
</Card>
</Col>
</Row>
</Form>
)
}
I want to be able to just put a field on top of my form to display "general" messages for the form, like my-special-error but I don't know where to get the errors from basically. When I'm using a toast (or any other notification method), this will disappear in a couple of seconds. I want to have a persistent method of showing an error inside of my form that is not directly related to a Input, but rather define a "custom-field-name" that can be used by the backend to provide more information on an error-state. lol I just solved this by trying to explain what I want to do 😄 🤷‍♂️ You can actually use a Form.Item with an ErrorList, but remove the errors={errors} Well thank you I guess 😄 🙏
exotic-emerald
exotic-emerald9mo ago
oh. thanks for the clear explanation. I'm glad the problem was solved 🙌🏻
probable-pink
probable-pink9mo ago
It's not the prettiest error, but I guess I can solve that with some Ant.D Styling:
No description
No description
exotic-emerald
exotic-emerald9mo ago
yes its require custom css or component :/
probable-pink
probable-pink9mo ago
Just for completeness sake, this was my solution:
type CustomAlertProps = {
namePath: string | string[];
};

const CustomAlert = (props: CustomAlertProps) => {
const formInstance = Form.useFormInstance();
const error = formInstance
.getFieldsError()
.filter(
(field) => field.errors.length && field.name.length === 1 && field.name[0] === props.namePath,
);
const extractedErrors = error.flatMap((error) => error.errors.map((error) => error));
return (
<div style={{ marginBottom: '1rem' }}>
<FormItem name={props.namePath} hidden />
{extractedErrors.map((errorMessage, index) => (
<Alert
message={errorMessage}
type="error"
key={`error-${index}`}
style={{ marginBottom: '1rem' }}
/>
))}
</div>
);
};
type CustomAlertProps = {
namePath: string | string[];
};

const CustomAlert = (props: CustomAlertProps) => {
const formInstance = Form.useFormInstance();
const error = formInstance
.getFieldsError()
.filter(
(field) => field.errors.length && field.name.length === 1 && field.name[0] === props.namePath,
);
const extractedErrors = error.flatMap((error) => error.errors.map((error) => error));
return (
<div style={{ marginBottom: '1rem' }}>
<FormItem name={props.namePath} hidden />
{extractedErrors.map((errorMessage, index) => (
<Alert
message={errorMessage}
type="error"
key={`error-${index}`}
style={{ marginBottom: '1rem' }}
/>
))}
</div>
);
};
As the ErrorList on top of the form is kind of ugly, I converted it into Alerts.
No description
probable-pink
probable-pink9mo ago
AND you have to have a <FormItem name={props.namePath} hidden /> for the field to be available through getFieldsError(), otherwise the error won't show up.