Skip to main content

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.

NOTE

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:

src/pages/products/list.tsx
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:

src/pages/products/create.tsx
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:

src/pages/products/edit.tsx
import { useForm, useSelect, Edit } from "@refinedev/antd";

import { Form, Input, Select, InputNumber } from "antd";

export const EditProduct = () => {
const { formProps, saveButtonProps, queryResult } = useForm({
refineCoreProps: {
redirect: "show",
},
});

const { selectProps } = useSelect({
resource: "categories",
defaultValue: queryResult?.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>
);
};
TIP

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:

src/pages/products/show.tsx
import { useShow, useOne } from "@refinedev/core";
import { TextField, NumberField, MarkdownField, Show } from "@refinedev/antd";

import { Typography } from "antd";

export const ShowProduct = () => {
const { queryResult: { 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.

Was this helpful?
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>
  );
};
installing dependencies
installing dependencies