useStepForm: display custom field error based on form submit to previous step

When we hit form submit, we get the list of field errors, we need to put the errors to previous step and go to step, that have the issue
11 Replies
kapa.ai
kapa.ai2w ago
Hi @viveksingh0143. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- To handle custom field errors and navigate to the step that has the issue when using useStepsForm, you can follow these steps: 1. Validate Fields on Submit: Use the validateFields method to validate the form fields when the form is submitted. 2. Navigate to the Step with Errors: If there are validation errors, navigate to the step that contains the erroneous field. Here's an example of how you can achieve this: ```tsx import { HttpError, useSelect } from "@refinedev/core";
import { useStepsForm } from "@refinedev/react-hook-form";
const PostCreate = () => {
const {
refineCore: { onFinish, formLoading },
register,
handleSubmit,
formState: { errors },
steps: { currentStep, gotoStep },
} = useStepsForm<IPost, HttpError, IPost>();
const { options } = useSelect<ICategory, HttpError>({
resource: "categories",
}); const renderFormByStep = (step: number) => {
switch (step) {
case 0:
return (
<>
<label>Title: </label>
<input
{...register("title", {
required: "This field is required",
})}
/>
{errors.title && <span>{errors.title.message}</span>}
</>
);
case 1:
return (
<>
<label>Status: </label>
<select {...register("status")}>
<option value="published">published</option>
<option value="draft">draft</option>
<option value="rejected">rejected</option>
</select>
</>
);
case 2:
return (
<>
<label>Category: </label>
<select
{...register("category.id", {
required: "This field is required",
})}
{options?.map((category) => (
<option key={category.value} value={category.value}>
{category.label}
</option>
))}
</select>
{errors.category && <span>{errors.category.message}</span>}
<br />
<br />
<label>Content: </label>
<textarea
{...register("content", {
required: "This field is required",
})}
rows={10}
cols={50}
/>
{errors.content && <span>{errors.content.message}</span>}
</>
);
}
}; const handleFormSubmit = async (data: IPost) => { try { await handleSubmit(onFinish)(data); } catch (error) { // Navigate to the step with the first error const firstErrorField = Object.keys(errors)[0]; if (firstErrorField) { const stepWithError = getStepWithError(firstErrorField); gotoStep(stepWithError); } } }; const getStepWithError = (field: string) => { switch (field) { case "title": return 0; case "status": return 1; case "category.id": case "content": return 2; default: return 0; } }; if (formLoading) { ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@940119979334967366> kapa.ai is still learning and improving, please let us know how it did by reacting below
desirable-aquamarine
desirable-aquamarine2w ago
@kapa.ai get the formState: { errors } inside useStepForm provided by antd
kapa.ai
kapa.ai2w ago
Let me check the knowledge base for you. One moment! :books: ---------------------- To get the formState: { errors } inside useStepsForm provided by Ant Design, you can use the useStepsForm hook which extends the functionalities of useForm. This means you can access the errors object directly from the formState. Here is an example of how you can access and use the errors object within useStepsForm: ```tsx import { HttpError, useSelect } from "@refinedev/core";
import { useStepsForm } from "@refinedev/react-hook-form";
const PostCreate = () => {
const {
refineCore: { onFinish, formLoading },
register,
handleSubmit,
formState: { errors },
steps: { currentStep, gotoStep },
} = useStepsForm<IPost, HttpError, IPost>();
const { options } = useSelect<ICategory, HttpError>({
resource: "categories",
}); const renderFormByStep = (step: number) => {
switch (step) {
case 0:
return (
<>
<label>Title: </label>
<input
{...register("title", {
required: "This field is required",
})}
/>
{errors.title && <span>{errors.title.message}</span>}
</>
);
case 1:
return (
<>
<label>Status: </label>
<select {...register("status")}>
<option value="published">published</option>
<option value="draft">draft</option>
<option value="rejected">rejected</option>
</select>
</>
);
case 2:
return (
<>
<label>Category: </label>
<select
{...register("category.id", {
required: "This field is required",
})}
{options?.map((category) => (
<option key={category.value} value={category.value}>
{category.label}
</option>
))}
</select>
{errors.category && <span>{errors.category.message}</span>}
<br />
<br />
<label>Content: </label>
<textarea
{...register("content", {
required: "This field is required",
})}
rows={10}
cols={50}
/>
{errors.content && <span>{errors.content.message}</span>}
</>
);
}
}; const handleFormSubmit = async (data: IPost) => { try { await handleSubmit(onFinish)(data); } catch (error) { // Navigate to the step with the first error const firstErrorField = Object.keys(errors)[0]; if (firstErrorField) { const stepWithError = getStepWithError(firstErrorField); gotoStep(stepWithError); } } }; const getStepWithError = (field: string) => { switch (field) { case "title": return 0; case "status": return 1; case "category.id": case "content": return 2; default: return 0; } }; if (formLoading) { return <div>Loading...</div>; ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@940119979334967366> kapa.ai is still learning and improving, please let us know how it did by reacting below
desirable-aquamarine
desirable-aquamarine2w ago
@kapa.ai there are no formState in useStepForm
kapa.ai
kapa.ai2w ago
Let me check the knowledge base for you. One moment! :books: ---------------------- You're correct that useStepsForm does not directly expose formState. However, you can still access the errors object from the formState by using the useForm hook, which useStepsForm extends. Here is an example of how you can handle form submission, validate fields, and navigate to the step with errors using useStepsForm with Ant Design:
import React from "react";
import { HttpError } from "@refinedev/core";
import { useStepsForm } from "@refinedev/antd";

const PostCreate = () => {
const {
refineCore: { onFinish, formLoading },
register,
handleSubmit,
formState: { errors },
steps: { currentStep, gotoStep },
} = useStepsForm<IPost, HttpError, IPost>();
const renderFormByStep = (step: number) => {
switch (step) {
case 0:
return (
<>
<label>Title: </label>
<input
{...register("title", {
required: "This field is required",
})}
/>
{errors.title && <span>{errors.title.message}</span>}
</>
);
case 1:
return (
<>
<label>Status: </label>
<select {...register("status")}>
<option value="published">published</option>
<option value="draft">draft</option>
<option value="rejected">rejected</option>
</select>
</>
);
case 2:
return (
<>
<label>Category: </label>
<select
{...register("category.id", {
required: "This field is required",
})}
>
{/* Options should be populated here */}
</select>
{errors.category && <span>{errors.category.message}</span>}
<br />
<br />
<label>Content: </label>
<textarea
{...register("content", {
required: "This field is required",
})}
rows={10}
cols={50}
/>
{errors.content && <span>{errors.content.message}</span>}
</>
);
}
};

const handleFormSubmit = async (data: IPost) => {
try {
await handleSubmit(onFinish)(data);
} catch (error) {
// Navigate to the step with the first error
const firstErrorField = Object.keys(errors)[0];
if (firstErrorField) {
const stepWithError = getStepWithError(firstErrorField);
gotoStep(stepWithError);
}
}
};
const getStepWithError = (field: string) => {
switch (field) {
case "title":
return 0;
case "status":
return 1;
case "category.id":
case "content":
return 2;
default:
return 0;
}
};

if (formLoading) {
return <div>Loading...</div>;
}

return (
<form onSubmit={handleFormSubmit}>
{renderFormByStep(currentStep)}
<button type="submit">Submit</button>
</form>
);
};

export default PostCreate;
import React from "react";
import { HttpError } from "@refinedev/core";
import { useStepsForm } from "@refinedev/antd";

const PostCreate = () => {
const {
refineCore: { onFinish, formLoading },
register,
handleSubmit,
formState: { errors },
steps: { currentStep, gotoStep },
} = useStepsForm<IPost, HttpError, IPost>();
const renderFormByStep = (step: number) => {
switch (step) {
case 0:
return (
<>
<label>Title: </label>
<input
{...register("title", {
required: "This field is required",
})}
/>
{errors.title && <span>{errors.title.message}</span>}
</>
);
case 1:
return (
<>
<label>Status: </label>
<select {...register("status")}>
<option value="published">published</option>
<option value="draft">draft</option>
<option value="rejected">rejected</option>
</select>
</>
);
case 2:
return (
<>
<label>Category: </label>
<select
{...register("category.id", {
required: "This field is required",
})}
>
{/* Options should be populated here */}
</select>
{errors.category && <span>{errors.category.message}</span>}
<br />
<br />
<label>Content: </label>
<textarea
{...register("content", {
required: "This field is required",
})}
rows={10}
cols={50}
/>
{errors.content && <span>{errors.content.message}</span>}
</>
);
}
};

const handleFormSubmit = async (data: IPost) => {
try {
await handleSubmit(onFinish)(data);
} catch (error) {
// Navigate to the step with the first error
const firstErrorField = Object.keys(errors)[0];
if (firstErrorField) {
const stepWithError = getStepWithError(firstErrorField);
gotoStep(stepWithError);
}
}
};
const getStepWithError = (field: string) => {
switch (field) {
case "title":
return 0;
case "status":
return 1;
case "category.id":
case "content":
return 2;
default:
return 0;
}
};

if (formLoading) {
return <div>Loading...</div>;
}

return (
<form onSubmit={handleFormSubmit}>
{renderFormByStep(currentStep)}
<button type="submit">Submit</button>
</form>
);
};

export default PostCreate;
In this example: - The errors object is accessed from formState. - The handleFormSubmit function handles form submission and navigates to the step with the first error if validation fails. For more details, you can refer to the [useStepsForm documentation](https://refine.dev/docs/ui-integrations/ant-design/hooks/use ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@940119979334967366> kapa.ai is still learning and improving, please let us know how it did by reacting below
desirable-aquamarine
desirable-aquamarine2w ago
useStepsForm | Refine
The useStepsForm hook allows you to split your form under an Ant Design based Steps component and provides you with a few useful functionalities that will help you manage your form.
kapa.ai
kapa.ai2w ago
Let me check the knowledge base for you. One moment! :books: ---------------------- To handle custom field errors and navigate to the step that has the issue when using useStepsForm with Ant Design, you can follow these steps: 1. Validate Fields on Submit: Use the validateFields method to validate the form fields when the form is submitted. 2. Navigate to the Step with Errors: If there are validation errors, navigate to the step that contains the erroneous field. Here is an example of how you can achieve this:
import React from "react";
import { HttpError } from "@refinedev/core";
import { useStepsForm } from "@refinedev/antd";

const PostCreate = () => {
const {
refineCore: { onFinish, formLoading },
register,
handleSubmit,
steps: { currentStep, gotoStep },
validateFields,
} = useStepsForm<IPost, HttpError, IPost>();
const renderFormByStep = (step: number) => {
switch (step) {
case 0:
return (
<>
<label>Title: </label>
<input
{...register("title", {
required: "This field is required",
})}
/>
</>
);
case 1:
return (
<>
<label>Status: </label>
<select {...register("status")}>
<option value="published">published</option>
<option value="draft">draft</option>
<option value="rejected">rejected</option>
</select>
</>
);
case 2:
return (
<>
<label>Category: </label>
<select
{...register("category.id", {
required: "This field is required",
})}
>
{/* Options should be populated here */}
</select>
<br />
<br />
<label>Content: </label>
<textarea
{...register("content", {
required: "This field is required",
})}
rows={10}
cols={50}
/>
</>
);
}
};

const handleFormSubmit = async (data: IPost) => {
try {
await handleSubmit(onFinish)(data);
} catch (error) {
// Validate fields and navigate to the step with the first error
const errors = await validateFields();
const firstErrorField = Object.keys(errors)[0];
if (firstErrorField) {
const stepWithError = getStepWithError(firstErrorField);
gotoStep(stepWithError);
}
}
};
const getStepWithError = (field: string) => {
switch (field) {
case "title":
return 0;
case "status":
return 1;
case "category.id":
case "content":
return 2;
default:
return 0;
}
};

if (formLoading) {
return <div>Loading...</div>;
}

return (
<form onSubmit={handleFormSubmit}>
{renderFormByStep(currentStep)}
<button type="submit">Submit</button>
</form>
);
};

export default PostCreate;
import React from "react";
import { HttpError } from "@refinedev/core";
import { useStepsForm } from "@refinedev/antd";

const PostCreate = () => {
const {
refineCore: { onFinish, formLoading },
register,
handleSubmit,
steps: { currentStep, gotoStep },
validateFields,
} = useStepsForm<IPost, HttpError, IPost>();
const renderFormByStep = (step: number) => {
switch (step) {
case 0:
return (
<>
<label>Title: </label>
<input
{...register("title", {
required: "This field is required",
})}
/>
</>
);
case 1:
return (
<>
<label>Status: </label>
<select {...register("status")}>
<option value="published">published</option>
<option value="draft">draft</option>
<option value="rejected">rejected</option>
</select>
</>
);
case 2:
return (
<>
<label>Category: </label>
<select
{...register("category.id", {
required: "This field is required",
})}
>
{/* Options should be populated here */}
</select>
<br />
<br />
<label>Content: </label>
<textarea
{...register("content", {
required: "This field is required",
})}
rows={10}
cols={50}
/>
</>
);
}
};

const handleFormSubmit = async (data: IPost) => {
try {
await handleSubmit(onFinish)(data);
} catch (error) {
// Validate fields and navigate to the step with the first error
const errors = await validateFields();
const firstErrorField = Object.keys(errors)[0];
if (firstErrorField) {
const stepWithError = getStepWithError(firstErrorField);
gotoStep(stepWithError);
}
}
};
const getStepWithError = (field: string) => {
switch (field) {
case "title":
return 0;
case "status":
return 1;
case "category.id":
case "content":
return 2;
default:
return 0;
}
};

if (formLoading) {
return <div>Loading...</div>;
}

return (
<form onSubmit={handleFormSubmit}>
{renderFormByStep(currentStep)}
<button type="submit">Submit</button>
</form>
);
};

export default PostCreate;
In this example: - The validateFields method is used to validate the form fields. - The handleFormSubmit function handles form submission and navigates to the step with the first error if validation fails. For more details, you can refer to the useStepsForm documentation (https://refine.dev/docs/ui-integrations/ant-design/hooks/use-steps-form/). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@940119979334967366> kapa.ai is still learning and improving, please let us know how it did by reacting below