Table - Admin Components

The listing pages in the Admin show a table with pagination.

Example of a table in the product listing page

To create a component that shows a table with pagination, create the file src/admin/components/table.tsx with the following content:

src/admin/components/table.tsx
1import { useMemo } from "react"2import { Table as UiTable } from "@medusajs/ui"3
4export type TableProps = {5  columns: {6    key: string7    label?: string8    render?: (value: unknown) => React.ReactNode9  }[]10  data: Record<string, unknown>[]11  pageSize: number12  count: number13  currentPage: number14  setCurrentPage: (value: number) => void15}16
17export const Table = ({18  columns,19  data,20  pageSize,21  count,22  currentPage,23  setCurrentPage,24}: TableProps) => {25  const pageCount = useMemo(() => {26    return Math.ceil(count / pageSize)27  }, [data, pageSize])28
29  const canNextPage = useMemo(() => {30    return currentPage < pageCount - 131  }, [currentPage, pageCount])32  const canPreviousPage = useMemo(() => {33    return currentPage - 1 >= 034  }, [currentPage])35
36  const nextPage = () => {37    if (canNextPage) {38      setCurrentPage(currentPage + 1)39    }40  }41
42  const previousPage = () => {43    if (canPreviousPage) {44      setCurrentPage(currentPage - 1)45    }46  }47
48  return (49    <div className="flex h-full flex-col overflow-hidden !border-t-0">50      <UiTable>51        <UiTable.Header>52          <UiTable.Row>53            {columns.map((column, index) => (54              <UiTable.HeaderCell key={index}>55                {column.label || column.key}56              </UiTable.HeaderCell>57            ))}58          </UiTable.Row>59        </UiTable.Header>60        <UiTable.Body>61          {data.map((item, index) => {62            const rowIndex = "id" in item ? item.id as string : index63            return (64              <UiTable.Row key={rowIndex}>65                {columns.map((column, index) => (66                  <UiTable.Cell key={`${rowIndex}-${index}`}>67                    <>68                      {column.render && column.render(item[column.key])}69                      {!column.render && (70                        <>{item[column.key] as string}</>71                      )}72                    </>73                  </UiTable.Cell>74                ))}75              </UiTable.Row>76            )77          })}78        </UiTable.Body>79      </UiTable>80      <UiTable.Pagination81        count={count}82        pageSize={pageSize}83        pageIndex={currentPage}84        pageCount={pageCount}85        canPreviousPage={canPreviousPage}86        canNextPage={canNextPage}87        previousPage={previousPage}88        nextPage={nextPage}89      />90    </div>91  )92}

The Table component uses the component from the UI package, with additional styling and rendering of data.

It accepts the following props:

columnsobject[]
The table's columns.
dataRecord<string, unknown>[]
The data to show in the table for the current page. The keys of each object should be in the columns array.
pageSizenumber
The number of items to show per page.
countnumber
The total number of items.
currentPagenumber
A zero-based index indicating the current page's number.
setCurrentPage(value: number) => void
A function used to change the current page.

Example#

Use the Table component in any widget or UI route.

For example, create the widget src/admin/widgets/product-widget.tsx with the following content:

src/admin/widgets/product-widget.tsx
1import { defineWidgetConfig } from "@medusajs/admin-sdk"2import { StatusBadge } from "@medusajs/ui"3import { Table } from "../components/table"4import { useState } from "react"5import { Container } from "../components/container"6
7const ProductWidget = () => {8  const [currentPage, setCurrentPage] = useState(0)9
10  return (11    <Container>12      <Table13        columns={[14          {15            key: "name",16            label: "Name",17          },18          {19            key: "is_enabled",20            label: "Status",21            render: (value: unknown) => {22              const isEnabled = value as boolean23
24              return (25                <StatusBadge color={isEnabled ? "green" : "grey"}>26                  {isEnabled ? "Enabled" : "Disabled"}27                </StatusBadge>28              )29            },30          },31        ]}32        data={[33          {34            name: "John",35            is_enabled: true,36          },37          {38            name: "Jane",39            is_enabled: false,40          },41        ]}42        pageSize={2}43        count={2}44        currentPage={currentPage}45        setCurrentPage={setCurrentPage}46      />47    </Container>48  )49}50
51export const config = defineWidgetConfig({52  zone: "product.details.before",53})54
55export default ProductWidget

This widget also uses the Container custom component.


Example With Data Fetching#

This section shows you how to use the Table component when fetching data from the Medusa application's API routes.

Assuming you've set up the JS SDK as explained in this guide, create the UI route src/admin/routes/custom/page.tsx with the following content:

src/admin/routes/custom/page.tsx
9import { Header } from "../../components/header"10
11const CustomPage = () => {12  const [currentPage, setCurrentPage] = useState(0)13  const limit = 1514  const offset = useMemo(() => {15    return currentPage * limit16  }, [currentPage])17
18  const { data } = useQuery({19    queryFn: () => sdk.admin.product.list({20      limit,21      offset22    }),23    queryKey: [["products", limit, offset]],24  })25
26  // TODO display table27}28
29export const config = defineRouteConfig({30  label: "Custom",31  icon: ChatBubbleLeftRight,32})33
34export default CustomPage

In the CustomPage component, you define:

  • A state variable currentPage that stores the current page of the table.
  • A limit variable, indicating how many items to retrieve per page
  • An offset memoized variable indicating how many items to skip before the retrieved items. It's calculated as a multiplication of currentPage and limit.

Then, you use useQuery from Tanstack Query to retrieve products using the JS SDK. You pass limit and offset as query parameters, and you set the queryKey, which is used for caching and revalidation, to be based on the key products, along with the current limit and offset. So, whenever the offset variable changes, the request is sent again to retrieve the products of the current page.

TipYou can change the query to send a request to a custom API route as explained in this guide.

useQuery returns an object containing data, which holds the response fields including the products and pagination fields.

Then, to display the table, replace the TODO with the following:

Code
1return (2  <SingleColumnLayout>3    <Container>4      <Header title="Products" />5      {data && (6        <Table7          columns={[8            {9              key: "id",10              label: "ID"11            },12            {13              key: "title",14              label: "Title"15            }16          ]}17          data={data.products as any}18          pageSize={data.limit}19          count={data.count}20          currentPage={currentPage}21          setCurrentPage={setCurrentPage}22        />23      )}24    </Container>25  </SingleColumnLayout>26)

Aside from the Table component, this UI route also uses the SingleColumnLayout, Container, and Header custom component.

If data isn't undefined, you display the Table component passing it the following props:

  • columns: The columns to show. You only show the product's ID and title.
  • data: The rows of the table. You pass it the products property of data.
  • pageSize: The maximum number of items per page. You pass it the count property of data.
  • currentPage and setCurrentPage: The current page and the function to change it.

To test it out, log into the Medusa Admin and open http://localhost:9000/app/custom. You'll find a table of products with pagination.

Was this page helpful?
Edit this page