useModalForm
useModalForm
hook also allows you to manage a form inside a modal component. It provides some useful methods to handle the form modal.
info
useModalForm
hook based on useForm
hook provided by @pankod/refine-mantine
.
Usage
We'll show two examples, one for creating and one for editing a post. Let's see how useModalForm
is used in both.
Create Modal
First, we'll create a list page for posts. We'll use the useTable
hook to manage the table and the useModalForm
hook to manage the form.
src/pages/posts/list.tsx
import React from "react";
import { useTable, ColumnDef, flexRender } from "@pankod/refine-react-table";
import {
List,
ScrollArea,
Table,
Pagination,
useModalForm,
} from "@pankod/refine-mantine";
import { CreatePostModal } from "../../components";
import { IPost } from "../../interfaces";
export const PostList: React.FC = () => {
const createModalForm = useModalForm({
refineCoreProps: { action: "create" },
initialValues: {
title: "",
status: "",
category: {
id: "",
},
content: "",
},
validate: {
title: (value) => (value.length < 2 ? "Too short title" : null),
status: (value) =>
value.length <= 0 ? "Status is required" : null,
category: {
id: (value) =>
value.length <= 0 ? "Category is required" : null,
},
content: (value) =>
value.length < 10 ? "Too short content" : null,
},
});
const {
modal: { show: showCreateModal },
} = createModalForm;
const columns = React.useMemo<ColumnDef<IPost>[]>(
() => [
{
id: "id",
header: "ID",
accessorKey: "id",
},
{
id: "title",
header: "Title",
accessorKey: "title",
},
{
id: "status",
header: "Status",
accessorKey: "status",
},
],
[],
);
const {
getHeaderGroups,
getRowModel,
refineCore: { setCurrent, pageCount, current },
} = useTable({
columns,
});
return (
<>
<CreatePostModal {...createModalForm} />
<ScrollArea>
<List createButtonProps={{ onClick: () => showCreateModal() }}>
<Table highlightOnHover>
<thead>
{getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th key={header.id}>
{!header.isPlaceholder && (
<div>
{flexRender(
header.column
.columnDef
.header,
header.getContext(),
)}
</div>
)}
</th>
);
})}
</tr>
))}
</thead>
<tbody>
{getRowModel().rows.map((row) => {
return (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => {
return (
<td key={cell.id}>
{flexRender(
cell.column.columnDef
.cell,
cell.getContext(),
)}
</td>
);
})}
</tr>
);
})}
</tbody>
</Table>
<br />
<Pagination
position="right"
total={pageCount}
page={current}
onChange={setCurrent}
/>
</List>
</ScrollArea>
</>
);
};
export interface IPost {
id: number;
title: string;
content: string;
status: "published" | "draft" | "rejected";
category: { id: number };
}
Now, let's see how the CreatePostModal
component is implemented.
src/components/createPostModal.tsx
import { BaseRecord, HttpError } from "@pankod/refine-core";
import {
UseModalFormReturnType,
Modal,
TextInput,
RichTextEditor,
Select,
useSelect,
Box,
SaveButton,
Text,
} from "@pankod/refine-mantine";
interface FormValues {
title: string;
content: string;
status: string;
category: { id: string };
}
export const CreatePostModal: React.FC<
UseModalFormReturnType<BaseRecord, HttpError, FormValues>
> = ({
getInputProps,
errors,
modal: { visible, close, title },
saveButtonProps,
}) => {
const { selectProps } = useSelect({
resource: "categories",
});
return (
<Modal opened={visible} onClose={close} title={title}>
<TextInput
mt={8}
label="Title"
placeholder="Title"
{...getInputProps("title")}
/>
<Select
mt={8}
label="Status"
placeholder="Pick one"
{...getInputProps("status")}
data={[
{ label: "Published", value: "published" },
{ label: "Draft", value: "draft" },
{ label: "Rejected", value: "rejected" },
]}
/>
<Select
mt={8}
label="Category"
placeholder="Pick one"
{...getInputProps("category.id")}
{...selectProps}
/>
<Text mt={8} weight={500} size="sm" color="#212529">
Content
</Text>
<RichTextEditor
sx={{ minHeight: 300 }}
{...getInputProps("content")}
/>
{errors.content && (
<Text mt={2} weight={500} size="xs" color="red">
{errors.content}
</Text>
)}
<Box mt={8} sx={{ display: "flex", justifyContent: "flex-end" }}>
<SaveButton {...saveButtonProps} />
</Box>
</Modal>
);
};
Edit Modal
Now, let's add the edit modal to the PostList
component.
src/pages/posts/list.tsx
import React from "react";
import { useTable, ColumnDef, flexRender } from "@pankod/refine-react-table";
import {
List,
ScrollArea,
Table,
Pagination,
EditButton,
useModalForm,
} from "@pankod/refine-mantine";
import { CreatePostModal, EditPostModal } from "../../components";
import { IPost } from "../../interfaces";
export const PostList: React.FC = () => {
const createModalForm = useModalForm({
refineCoreProps: { action: "create" },
initialValues: {
title: "",
status: "",
category: {
id: "",
},
content: "",
},
validate: {
title: (value) => (value.length < 2 ? "Too short title" : null),
status: (value) =>
value.length <= 0 ? "Status is required" : null,
category: {
id: (value) =>
value.length <= 0 ? "Category is required" : null,
},
content: (value) =>
value.length < 10 ? "Too short content" : null,
},
});
const {
modal: { show: showCreateModal },
} = createModalForm;
const editModalForm = useModalForm({
refineCoreProps: { action: "edit" },
initialValues: {
title: "",
status: "",
category: {
id: "",
},
content: "",
},
validate: {
title: (value) => (value.length < 2 ? "Too short title" : null),
status: (value) =>
value.length <= 0 ? "Status is required" : null,
category: {
id: (value) =>
value.length <= 0 ? "Category is required" : null,
},
content: (value) =>
value.length < 10 ? "Too short content" : null,
},
});
const {
modal: { show: showEditModal },
} = editModalForm;
const columns = React.useMemo<ColumnDef<IPost>[]>(
() => [
{
id: "id",
header: "ID",
accessorKey: "id",
},
{
id: "title",
header: "Title",
accessorKey: "title",
},
{
id: "status",
header: "Status",
accessorKey: "status",
},
{
id: "actions",
header: "Actions",
accessorKey: "id",
cell: function render({ getValue }) {
return (
<EditButton
hideText
size="xs"
onClick={() => showEditModal(getValue() as number)}
/>
);
},
},
],
[],
);
const {
getHeaderGroups,
getRowModel,
refineCore: { setCurrent, pageCount, current },
} = useTable({
columns,
});
return (
<>
<CreatePostModal {...createModalForm} />
<EditPostModal {...editModalForm} />
<ScrollArea>
<List createButtonProps={{ onClick: () => showCreateModal() }}>
<Table highlightOnHover>
<thead>
{getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th key={header.id}>
{!header.isPlaceholder && (
<div>
{flexRender(
header.column
.columnDef
.header,
header.getContext(),
)}
</div>
)}
</th>
);
})}
</tr>
))}
</thead>
<tbody>
{getRowModel().rows.map((row) => {
return (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => {
return (
<td key={cell.id}>
{flexRender(
cell.column.columnDef
.cell,
cell.getContext(),
)}
</td>
);
})}
</tr>
);
})}
</tbody>
</Table>
<br />
<Pagination
position="right"
total={pageCount}
page={current}
onChange={setCurrent}
/>
</List>
</ScrollArea>
</>
);
};
Finally, let's see how the EditPostModal
component is implemented.
src/components/editPostModal.tsx
import { BaseRecord, HttpError } from "@pankod/refine-core";
import {
UseModalFormReturnType,
Modal,
TextInput,
RichTextEditor,
Select,
useSelect,
SaveButton,
Box,
Text,
} from "@pankod/refine-mantine";
interface FormValues {
title: string;
content: string;
status: string;
category: { id: string };
}
export const EditPostModal: React.FC<
UseModalFormReturnType<BaseRecord, HttpError, FormValues>
> = ({
getInputProps,
errors,
modal: { visible, close, title },
refineCore: { queryResult },
saveButtonProps,
}) => {
const { selectProps } = useSelect({
resource: "categories",
defaultValue: queryResult?.data?.data.category.id,
});
return (
<Modal opened={visible} onClose={close} title={title}>
<TextInput
mt={8}
label="Title"
placeholder="Title"
{...getInputProps("title")}
/>
<Select
mt={8}
label="Status"
placeholder="Pick one"
{...getInputProps("status")}
data={[
{ label: "Published", value: "published" },
{ label: "Draft", value: "draft" },
{ label: "Rejected", value: "rejected" },
]}
/>
<Select
mt={8}
label="Category"
placeholder="Pick one"
{...getInputProps("category.id")}
{...selectProps}
/>
<Text mt={8} weight={500} size="sm" color="#212529">
Content
</Text>
<RichTextEditor {...getInputProps("content")} />
{errors.content && (
<Text mt={2} weight={500} size="xs" color="red">
{errors.content}
</Text>
)}
<Box mt={8} sx={{ display: "flex", justifyContent: "flex-end" }}>
<SaveButton {...saveButtonProps} />
</Box>
</Modal>
);
};
API Reference
Properties
Property | Description | Type |
---|---|---|
modalProps | Configuration object for the modal or drawer | ModalPropsType |
refineCoreProps | Configuration object for the core of the useForm | UseFormProps |
@mantine/form 's useForm properties | See useForm documentation |
ModalPropsType
Property Description Type Default defaultVisible Initial visibility state of the modal boolean
false
autoSubmitClose Whether the form should be submitted when the modal is closed boolean
true
autoResetForm Whether the form should be reset when the form is submitted boolean
true
Return values
Property | Description | Type |
---|---|---|
modal | Relevant states and methods to control the modal or drawer | ModalReturnValues |
refineCore | The return values of the useForm in the core | UseFormReturnValues |
@mantine/form 's useForm return values | See useForm documentation |
ModalReturnValues
Property Description Type visible State of modal visibility boolean
show Sets the visible state to true (id?: BaseKey) => void
close Sets the visible state to false () => void
submit Submits the form (values: TVariables) => void
title Modal title based on resource and action value string
saveButtonProps Props for a submit button { disabled: boolean, onClick: (e: React.FormEvent<HTMLFormElement>) => void; }