useStepsForm
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.
import { useStepsForm } from "@pankod/refine";
const { stepsProps, formProps } = useStepsForm<IPost>();
All we have to do is to pass the props it returns to our <Steps>
and <Form>
components.
Usage
We'll do two examples, one for creating and one for editing a post. Let's see how useStepsForm
is used in both.
Create
For the sake of simplicity, in this example we're going to build a Post
create form that consists of only a title
and a relational category
field.
To split your form items under a <Steps>
component, first import and use useStepsForm
hook in your page:
import { useStepsForm } from "@pankod/refine";
export const PostCreate: React.FC = () => {
const {
current,
gotoStep,
stepsProps,
formProps,
saveButtonProps,
queryResult,
} = useStepsForm<IPost>();
return null;
};
interface ICategory {
id: string;
title: string;
}
interface IPost {
id: string;
title: string;
status: "published" | "draft" | "rejected";
}
useStepsForm
is generic over the type form data to help you type check your code.
This hook returns a set of useful values to render steps form. Given current
value, you should have a way to render your form items conditionally with this index value. You can use an array to achieve this.
Here, each item of formList
corresponds to one step in form:
import { useStepsForm, useSelect, Form, Input, Select } from "@pankod/refine";
export const PostCreate: React.FC = () => {
const {
current,
gotoStep,
stepsProps,
formProps,
saveButtonProps,
} = useStepsForm<IPost>();
const { selectProps: categorySelectProps } = useSelect<ICategory>({
resource: "categories",
});
const formList = [
<>
<Form.Item
label="Title"
name="title"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
</>,
<>
<Form.Item
label="Category"
name={["category", "id"]}
rules={[
{
required: true,
},
]}
>
<Select {...categorySelectProps} />
</Form.Item>
</>,
];
return null;
};
Since category
is a relational data, we use useSelect
to fetch its data.
You should use stepsProps
on <Steps>
component, formProps
on the <Form>
component respectively. And as the last step, you should render the <Steps>
component besides the form like this:
import {
useStepsForm,
useSelect,
Form,
Input,
Select,
Create,
Steps,
} from "@pankod/refine";
export const PostCreate: React.FC = () => {
const {
current,
gotoStep,
stepsProps,
formProps,
saveButtonProps,
queryResult,
} = useStepsForm<IPost>();
const { selectProps: categorySelectProps } = useSelect<ICategory>({
resource: "categories",
});
const formList = [
<>
<Form.Item
label="Title"
name="title"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
</>,
<>
<Form.Item
label="Category"
name={["category", "id"]}
rules={[
{
required: true,
},
]}
>
<Select {...categorySelectProps} />
</Form.Item>
</>,
];
return (
<Create saveButtonProps={saveButtonProps}>
<Steps {...stepsProps}>
<Steps.Step title="First Step" />
<Steps.Step title="Second Step" />
</Steps>
<Form {...formProps} layout="vertical">
{formList[current]}
</Form>
</Create>
);
};
Make sure to add as much <Steps.Step>
components as the number of steps in the formList
array.
To help users navigate between steps in the form, you can use action buttons. Your navigation buttons should use the gotoStep
function that was previously returned from the the useStepsForm
hook.
import {
useStepsForm,
useSelect,
Form,
Input,
Select,
Create,
Steps,
Button,
SaveButton,
} from "@pankod/refine";
export const PostCreate: React.FC = () => {
const {
current,
gotoStep,
stepsProps,
formProps,
saveButtonProps,
queryResult,
submit,
} = useStepsForm<IPost>();
const { selectProps: categorySelectProps } = useSelect<ICategory>({
resource: "categories",
});
const formList = [
<>
<Form.Item
label="Title"
name="title"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
</>,
<>
<Form.Item
label="Category"
name={["category", "id"]}
rules={[
{
required: true,
},
]}
>
<Select {...categorySelectProps} />
</Form.Item>
</>,
];
return (
<Create
actionButtons={
<>
{current > 0 && (
<Button
onClick={() => {
gotoStep(current - 1);
}}
>
Previous
</Button>
)}
{current < formList.length - 1 && (
<Button
onClick={() => {
gotoStep(current + 1);
}}
>
Next
</Button>
)}
{current === formList.length - 1 && (
<SaveButton
{...saveButtonProps}
style={{ marginRight: 10 }}
onClick={() => submit()}
/>
)}
</>
}
>
<Steps {...stepsProps}>
<Steps.Step title="First Step" />
<Steps.Step title="Second Step" />
</Steps>
<Form {...formProps} layout="vertical">
{formList[current]}
</Form>
</Create>
);
};
Edit
In this example, we'll just look at what's different from the example above.
import {
useStepsForm,
useSelect,
Form,
Input,
Select,
Steps,
Button,
SaveButton,
Edit,
} from "@pankod/refine";
export const PostCreate: React.FC = () => {
const {
current,
gotoStep,
stepsProps,
formProps,
saveButtonProps,
queryResult,
submit,
} = useStepsForm<IPost>();
const postData = queryResult?.data?.data;
const { selectProps: categorySelectProps } = useSelect<ICategory>({
resource: "categories",
defaultValue: postData?.category.id,
});
const formList = [
<>
<Form.Item
label="Title"
name="title"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
</>,
<>
<Form.Item
label="Category"
name={["category", "id"]}
rules={[
{
required: true,
},
]}
>
<Select {...categorySelectProps} />
</Form.Item>
</>,
];
return (
<Edit
actionButtons={
<>
{current > 0 && (
<Button
onClick={() => {
gotoStep(current - 1);
}}
>
Previous
</Button>
)}
{current < formList.length - 1 && (
<Button
onClick={() => {
gotoStep(current + 1);
}}
>
Next
</Button>
)}
{current === formList.length - 1 && (
<SaveButton
{...saveButtonProps}
style={{ marginRight: 10 }}
onClick={() => submit()}
/>
)}
</>
}
>
<Steps {...stepsProps}>
<Steps.Step title="First Step" />
<Steps.Step title="Second Step" />
</Steps>
<Form {...formProps} layout="vertical">
{formList[current]}
</Form>
</Edit>
);
};
API Reference
Properties
Key | Description | Type | Default |
---|---|---|---|
action Required | Type of form mode | "edit" | "create" | "create" |
defaultCurrent | Default step, counting from 0 | number | 0 |
total | Total count of steps steps | number | 0 |
isBackValidate | Should validate if went to the previous step step | boolean | true |
form | Ant Design form instance | FormInstance<TVariables> | |
mutationMode | Determines when mutations are executed. If not explicitly configured, it is read from the mutation mode config of the resource in current route | "pessimistic" | "optimistic" | "undoable" | |
onMutationError | Called when a mutation encounters an error | (error: any, variables: any, context: any) => void | |
onMutationSuccess | Called when a mutation is successful | (data: UpdateResponse<M>, variables: any, context: any) => void | |
redirect | Page to redirect after a succesfull mutation | "show | "edit | "list" | false | "list" |
submit | Submit the form | (values?: TVariables) => Promise<TData> | |
submitOnEnter | Listen Enter key press to submit form | boolean | false |
undoableTimeout | Duration to wait before executing mutations when mutationMode = "undoable" | number | 5000 * |
warnWhenUnsavedChanges | Shows notification when unsaved changes exist | boolean | false * |
successNotification | Successful Mutation notification | SuccessErrorNotification | "Successfully created resource " or "Successfully updated resource " |
errorNotification | Unsuccessful Mutation notification | SuccessErrorNotification | "There was an error creating resource (status code: statusCode )" or "Error when updating resource (status code: statusCode )" |
metaData | Metadata query for dataProvider | MetaDataQuery | {} |
liveMode | Whether to update data automatically ("auto" ) or not ("manual" ) if a related live event is received. The "off" value is used to avoid creating a subscription. | "auto" | "manual" | "off" | "off" |
liveParams | Params to pass to liveProvider 's subscribe method if liveMode is enabled. | { ids?: string[]; [key: string]: any; } | undefined |
onLiveEvent | Callback to handle all related live events of this hook. | (event: LiveEvent) => void | undefined |
*
: These props have default values inRefineContext
and can also be set on <Refine> component.useModalForm
will use what is passed to<Refine>
as default but a local value will override it.
**
: If not explicitly configured, default value ofredirect
depends on whichaction
used. Ifaction
iscreate
,redirect
s default value isedit
(created resources edit page). ifaction
isedit
instead,redirect
s default value islist
.
Return Values
Key | Description | Type |
---|---|---|
stepsProps | Ant Design steps props | StepsProps |
current | Current step, counting from 0. | number |
gotoStep | Go to the target step | (step: number) => void |
formProps | Ant Design form props | FormProps |
form | Ant Design form instance | FormInstance<TVariables> |
formLoading | Loading status of form | boolean |
defaultFormValuesLoading | DefaultFormValues loading status of form | boolean |
submit | Submit method, the parameter is the value of the form fields | () => void |
saveButtonProps | Props for a submit button | { disabled: boolean; onClick: () => void; loading: boolean; } |
queryResult | Result of the query of a record | QueryObserverResult<{ data: TData }> |
mutationResult | Result of the mutation triggered by submitting the form | UseMutationResult< { data: TData }, TError, { resource: string; values: TVariables; }, unknown> ](https://react-query.tanstack.com/reference/useMutation) |
editId | Record id for edit action | string |
setEditId | editId setter | Dispatch<SetStateAction< string | undefined>> |
cloneId | Record id for clone action | string |
setCloneId | cloneId setter | Dispatch<SetStateAction< string | undefined>> |
Type Parameters
Property | Desription | Default |
---|---|---|
TData | Result data of the query that extends BaseRecord | BaseRecord |
TError | Custom error object that extends HttpError | HttpError |
TVariables | Values for params. | {} |