Skip to main content

CRUD Components

After refactoring the components, it's time to learn about the CRUD view components provided by Refine's Material UI integration. These components are implemented to provide consistent design with Material UI 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 React from "react";
import { useSelect } from "@refinedev/core";
import { List, useDataGrid, EditButton, ShowButton } from "@refinedev/mui";

import {
DataGrid,
GridColDef,
GridValueFormatterParams,
} from "@mui/x-data-grid";

export const ListProducts = () => {
const { dataGridProps } = useDataGrid<IProduct>({
sorters: { initial: [{ field: "id", order: "asc" }] },
syncWithLocation: true,
});

const {
options: categories,
query: { isLoading },
} = useSelect<ICategory>({
resource: "categories",
pagination: false,
});

const columns = React.useMemo<GridColDef<IProduct>[]>(
() => [
/* ... */
],
[categories, isLoading],
);

return (
<List>
<DataGrid {...dataGridProps} columns={columns} autoHeight />
</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 } from "@refinedev/react-hook-form";
import { Create, useAutocomplete } from "@refinedev/mui";

import Box from "@mui/material/Box";
import TextField from "@mui/material/TextField";
import Autocomplete from "@mui/material/Autocomplete";

import { Controller } from "react-hook-form";

export const CreateProduct = () => {
const {
register,
control,
saveButtonProps,
formState: { errors },
} = useForm();

const { autocompleteProps } = useAutocomplete({
resource: "categories",
});

return (
<Create saveButtonProps={saveButtonProps}>
<Box
component="form"
sx={{ display: "flex", flexDirection: "column", gap: "12px" }}
>
{/* ... */}
</Box>
</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 } from "@refinedev/react-hook-form";
import { Edit, useAutocomplete } from "@refinedev/mui";

import Box from "@mui/material/Box";
import TextField from "@mui/material/TextField";
import Autocomplete from "@mui/material/Autocomplete";

import { Controller } from "react-hook-form";

export const EditProduct = () => {
const {
register,
control,
saveButtonProps,
refineCore: { query },
formState: { errors },
} = useForm();

const { autocompleteProps } = useAutocomplete({
resource: "categories",
defaultValue: query?.data?.data?.category?.id,
});

return (
<Edit saveButtonProps={saveButtonProps}>
<Box
component="form"
sx={{ display: "flex", flexDirection: "column", gap: "12px" }}
autoComplete="off"
>
{/* ... */}
</Box>
</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 {
Show,
TextFieldComponent as TextField,
NumberField,
MarkdownField,
} from "@refinedev/mui";

import Typography from "@mui/material/Typography";
import Stack from "@mui/material/Stack";

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,
},
});

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

return (
<Show>
<Stack gap={1}>
<Typography variant="body1" fontWeight="bold">
Id
</Typography>
<TextField value={data?.data?.id} />

<Typography variant="body1" fontWeight="bold">
Name
</Typography>
<TextField value={data?.data?.name} />

<Typography variant="body1" fontWeight="bold">
Description
</Typography>
<MarkdownField value={data?.data?.description} />

<Typography variant="body1" fontWeight="bold">
Material
</Typography>
<TextField value={data?.data?.material} />

<Typography variant="body1" fontWeight="bold">
Category
</Typography>
<TextField
value={categoryIsLoading ? "Loading..." : categoryData?.data?.title}
/>

<Typography variant="body1" fontWeight="bold">
Price
</Typography>
<NumberField value={data?.data?.price} />
</Stack>
</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 Material UI's notification elements and notistack.

Was this helpful?
import React from "react";
import { useSelect } from "@refinedev/core";
import { useDataGrid, EditButton, ShowButton } from "@refinedev/mui";

import {
  DataGrid,
  GridColDef,
  GridValueFormatterParams,
} from "@mui/x-data-grid";

export const ListProducts = () => {
  const { dataGridProps } = useDataGrid<IProduct>({
    sorters: { initial: [{ field: "id", order: "asc" }] },
    syncWithLocation: true,
  });

  const {
    options: categories,
    query: { isLoading },
  } = useSelect<ICategory>({
    resource: "categories",
    pagination: false,
  });

  const columns = React.useMemo<GridColDef<IProduct>[]>(
    () => [
      {
        field: "id",
        headerName: "ID",
        type: "number",
        width: 50,
      },
      {
        field: "name",
        headerName: "Name",
        minWidth: 400,
        flex: 1,
      },
      {
        field: "category.id",
        headerName: "Category",
        minWidth: 250,
        flex: 0.5,
        type: "singleSelect",
        valueOptions: categories,
        valueFormatter: (params) => params?.value,
        renderCell: function render({ row }) {
          if (isLoading) {
            return "Loading...";
          }

          return categories?.find(
            (category) => category.value == row.category.id,
          )?.label;
        },
      },
      {
        field: "material",
        headerName: "Material",
        minWidth: 120,
        flex: 0.3,
      },
      {
        field: "price",
        headerName: "Price",
        minWidth: 120,
        flex: 0.3,
      },
      {
        field: "actions",
        headerName: "Actions",
        renderCell: function render({ row }) {
          return (
            <div>
              <EditButton hideText recordItemId={row.id} />
              <ShowButton hideText recordItemId={row.id} />
            </div>
          );
        },
      },
    ],
    [categories, isLoading],
  );

  return (
    <div>
      <h1>Products</h1>
      <DataGrid {...dataGridProps} columns={columns} autoHeight />
    </div>
  );
};

interface IProduct {
  id: number;
  name: string;
  material: string;
  price: string;
  category: ICategory;
}

interface ICategory {
  id: number;
  title: string;
}
installing dependencies
installing dependencies