CRUD Components
After refactoring the components, it's time to learn about the CRUD view components provided by Refine's Ant Design integration. These components are implemented to provide consistent design with Ant Design with additional features such as headers with i18n support, breadcrumbs, navigation buttons, submission buttons, and more.
These components are not required to use Refine, but they are useful for building a consistent user interface without worrying about the common features you need across your application.
List View
The <List />
component is a wrapper component for list pages which provides an header with i18n support and navigation to create a new record. You can always provide more features and elements by passing customizing the component.
Remember that when we've removed the <Header />
component, we've also removed the navigation link for the /products/create
route. <List />
will automatically add a button for this purpose without needing any additional code.
Update your src/pages/products/list.tsx
file by adding the following lines:
import { useMany, getDefaultFilter } from "@refinedev/core";
import {
useTable,
EditButton,
ShowButton,
getDefaultSortOrder,
FilterDropdown,
useSelect,
List,
} from "@refinedev/antd";
import { Table, Space, Input, Select } from "antd";
export const ListProducts = () => {
const { tableProps, sorters, filters } = useTable({
sorters: { initial: [{ field: "id", order: "asc" }] },
filters: {
initial: [
{ field: "name", operator: "contains", value: "" },
{ field: "category.id", operator: "in", value: [1, 2] },
],
},
syncWithLocation: true,
});
const { data: categories, isLoading } = useMany({
resource: "categories",
ids: tableProps?.dataSource?.map((product) => product.category?.id) ?? [],
});
const { selectProps } = useSelect({
resource: "categories",
});
return (
<List>
<Table {...tableProps} rowKey="id">
{/* ... */}
</Table>
</List>
);
};
Create View
The <Create />
component is a wrapper component for create pages. It provides an header with i18n support and navigation to list view, a back button and breadcrumbs. It includes a <SaveButton />
at the footer that you can pass saveButtonProps
from the useForm
hook to submit your forms. You can always provide more features and elements by passing customizing the component.
Update your src/pages/products/create.tsx
file by adding the following lines:
import { useForm, useSelect, Create } from "@refinedev/antd";
import { Form, Input, Select, InputNumber } from "antd";
export const CreateProduct = () => {
const { formProps, saveButtonProps } = useForm({
refineCoreProps: {
redirect: "edit",
},
});
const { selectProps } = useSelect({
resource: "categories",
});
return (
<Create saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
<Form.Item label="Name" name="name">
<Input />
</Form.Item>
<Form.Item label="Description" name="description">
<Input.TextArea />
</Form.Item>
<Form.Item label="Material" name="material">
<Input />
</Form.Item>
<Form.Item label="Category" name={["category", "id"]}>
<Select {...selectProps} />
</Form.Item>
<Form.Item label="Price" name="price">
<InputNumber step="0.01" stringMode />
</Form.Item>
</Form>
</Create>
);
};
Edit View
The <Edit />
component is a wrapper component for edit pages. The design and the usage is similar to the <Create />
component. Additionally, it includes the <RefreshButton />
and <DeleteButton />
at its header. You can always provide more features and elements by passing customizing the component.
Update your src/pages/products/edit.tsx
file by adding the following lines:
import { useForm, useSelect, Edit } from "@refinedev/antd";
import { Form, Input, Select, InputNumber } from "antd";
export const EditProduct = () => {
const { formProps, saveButtonProps, query } = useForm({
refineCoreProps: {
redirect: "show",
},
});
const { selectProps } = useSelect({
resource: "categories",
defaultValue: query?.data?.data?.category?.id,
});
return (
<Edit saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
<Form.Item label="Name" name="name">
<Input />
</Form.Item>
<Form.Item label="Description" name="description">
<Input.TextArea />
</Form.Item>
<Form.Item label="Material" name="material">
<Input />
</Form.Item>
<Form.Item label="Category" name={["category", "id"]}>
<Select {...selectProps} />
</Form.Item>
<Form.Item label="Price" name="price">
<InputNumber step="0.01" stringMode />
</Form.Item>
</Form>
</Edit>
);
};
Notice that we've removed the <SaveButton />
component from the <EditProduct />
and used the saveButtonProps
prop to provide the same functionality with the <Edit />
component.
Show View
The <Show />
component is a wrapper component for show pages.It provides a header with i18n support and navigation to the list view, edit the record, a refresh button, a delete button, a back button, and breadcrumbs. You can always provide more features and elements by passing customizing the component.
Update your src/pages/products/show.tsx
file by adding the following lines:
import { useShow, useOne } from "@refinedev/core";
import { TextField, NumberField, MarkdownField, Show } from "@refinedev/antd";
import { Typography } from "antd";
export const ShowProduct = () => {
const { query: { data, isLoading } } = useShow();
const { data: categoryData, isLoading: categoryIsLoading } =
useOne({
resource: "categories",
id: data?.data?.category.id || "",
queryOptions: {
enabled: !!data?.data,
},
});
return (
<Show isLoading={isLoading}>
<Typography.Title level={5}>Id</Typography.Title>
<TextField value={data?.data?.id} />
<Typography.Title level={5}>Name</Typography.Title>
<TextField value={data?.data?.name} />
<Typography.Title level={5}>Description</Typography.Title>
<MarkdownField value={data?.data?.description} />
<Typography.Title level={5}>Material</Typography.Title>
<TextField value={data?.data?.material} />
<Typography.Title level={5}>Category</Typography.Title>
<TextField
value={categoryIsLoading ? "Loading..." : categoryData?.data?.title}
/>
<Typography.Title level={5}>Price</Typography.Title>
<NumberField value={data?.data?.price} />
</Show>
);
};
Now our application has a consistent design with many features ready to use without writing a single line of code.
In the next step, we will learn how to handle notifications with Refine and integrate it with Ant Design's notification system.
import { useMany, getDefaultFilter } from "@refinedev/core"; import { useTable, EditButton, ShowButton, getDefaultSortOrder, FilterDropdown, useSelect, } from "@refinedev/antd"; import { Table, Space, Input, Select } from "antd"; export const ListProducts = () => { const { tableProps, sorters, filters } = useTable({ sorters: { initial: [{ field: "id", order: "asc" }] }, filters: { initial: [{ field: "category.id", operator: "eq", value: 2 }], }, syncWithLocation: true, }); const { data: categories, isLoading } = useMany({ resource: "categories", ids: tableProps?.dataSource?.map((product) => product.category?.id) ?? [], }); const { selectProps } = useSelect({ resource: "categories", defaultValue: getDefaultFilter("category.id", filters, "eq"), }); return ( <div> <h1>Products</h1> <Table {...tableProps} rowKey="id"> <Table.Column dataIndex="id" title="ID" sorter defaultSortOrder={getDefaultSortOrder("id", sorters)} /> <Table.Column dataIndex="name" title="Name" sorter defaultSortOrder={getDefaultSortOrder("name", sorters)} filterDropdown={(props) => ( <FilterDropdown {...props}> <Input /> </FilterDropdown> )} /> <Table.Column dataIndex={["category", "id"]} title="Category" render={(value) => { if (isLoading) { return "Loading..."; } return categories?.data?.find((category) => category.id == value) ?.title; }} filterDropdown={(props) => ( <FilterDropdown {...props} mapValue={(selectedKey) => Number(selectedKey)} > <Select style={{ minWidth: 200 }} {...selectProps} /> </FilterDropdown> )} defaultFilteredValue={getDefaultFilter("category.id", filters, "eq")} /> <Table.Column dataIndex="material" title="Material" /> <Table.Column dataIndex="price" title="Price" /> <Table.Column title="Actions" render={(_, record) => ( <Space> <ShowButton hideText size="small" recordItemId={record.id} /> <EditButton hideText size="small" recordItemId={record.id} /> </Space> )} /> </Table> </div> ); };