---
sidebar_label: "Table"
---

import { TypeList } from "docs-ui"

export const metadata = {
  title: `Table - Admin Components`,
}

# {metadata.title}

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

![Example of a table in the product listing page](https://res.cloudinary.com/dza7lstvk/image/upload/v1728295658/Medusa%20Resources/list_ddt9zc.png)

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

```tsx title="src/admin/components/table.tsx"
import { useMemo } from "react"
import { Table as UiTable } from "@medusajs/ui"

export type TableProps = {
  columns: {
    key: string
    label?: string
    render?: (value: unknown) => React.ReactNode
  }[]
  data: Record<string, unknown>[]
  pageSize: number
  count: number
  currentPage: number
  setCurrentPage: (value: number) => void
}

export const Table = ({
  columns,
  data,
  pageSize,
  count,
  currentPage,
  setCurrentPage,
}: TableProps) => {
  const pageCount = useMemo(() => {
    return Math.ceil(count / pageSize)
  }, [data, pageSize])

  const canNextPage = useMemo(() => {
    return currentPage < pageCount - 1
  }, [currentPage, pageCount])
  const canPreviousPage = useMemo(() => {
    return currentPage - 1 >= 0
  }, [currentPage])

  const nextPage = () => {
    if (canNextPage) {
      setCurrentPage(currentPage + 1)
    }
  }

  const previousPage = () => {
    if (canPreviousPage) {
      setCurrentPage(currentPage - 1)
    }
  }

  return (
    <div className="flex h-full flex-col overflow-hidden !border-t-0">
      <UiTable>
        <UiTable.Header>
          <UiTable.Row>
            {columns.map((column, index) => (
              <UiTable.HeaderCell key={index}>
                {column.label || column.key}
              </UiTable.HeaderCell>
            ))}
          </UiTable.Row>
        </UiTable.Header>
        <UiTable.Body>
          {data.map((item, index) => {
            const rowIndex = "id" in item ? item.id as string : index
            return (
              <UiTable.Row key={rowIndex}>
                {columns.map((column, index) => (
                  <UiTable.Cell key={`${rowIndex}-${index}`}>
                    <>
                      {column.render && column.render(item[column.key])}
                      {!column.render && (
                        <>{item[column.key] as string}</>
                      )}
                    </>
                  </UiTable.Cell>
                ))}
              </UiTable.Row>
            )
          })}
        </UiTable.Body>
      </UiTable>
      <UiTable.Pagination
        count={count}
        pageSize={pageSize}
        pageIndex={currentPage}
        pageCount={pageCount}
        canPreviousPage={canPreviousPage}
        canNextPage={canNextPage}
        previousPage={previousPage}
        nextPage={nextPage}
      />
    </div>
  )
}
```

The `Table` component uses the component from the [UI package](!ui!/components/table), with additional styling and rendering of data.

It accepts the following props:

<TypeList
  types={[
    {
      name: "columns",
      type: "`object[]`",
      optional: false,
      description: "The table's columns.",
      children: [
        {
          name: "key",
          type: "`string`",
          optional: false,
          description: "The column's key in the passed `data`"
        },
        {
          name: "label",
          type: "`string`",
          optional: true,
          description: "The column's label shown in the table. If not provided, the `key` is used."
        },
        {
          name: "render",
          type: "`(value: unknown) => React.ReactNode`",
          optional: true,
          description: "By default, the data is shown as-is in the table. You can use this function to change how the value is rendered. The function receives the value is a parameter and returns a React node."
        }
      ]
    },
    {
      name: "data",
      type: "`Record<string, unknown>[]`",
      optional: false,
      description: "The data to show in the table for the current page. The keys of each object should be in the `columns` array."
    },
    {
      name: "pageSize",
      type: "`number`",
      optional: false,
      description: "The number of items to show per page."
    },
    {
      name: "count",
      type: "`number`",
      optional: false,
      description: "The total number of items."
    },
    {
      name: "currentPage",
      type: "`number`",
      optional: false,
      description: "A zero-based index indicating the current page's number."
    },
    {
      name: "setCurrentPage",
      type: "`(value: number) => void`",
      optional: false,
      description: "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:

```tsx title="src/admin/widgets/product-widget.tsx"
import { defineWidgetConfig } from "@medusajs/admin-sdk"
import { StatusBadge } from "@medusajs/ui"
import { Table } from "../components/table"
import { useState } from "react"
import { Container } from "../components/container"

const ProductWidget = () => {
  const [currentPage, setCurrentPage] = useState(0)

  return (
    <Container>
      <Table
        columns={[
          {
            key: "name",
            label: "Name",
          },
          {
            key: "is_enabled",
            label: "Status",
            render: (value: unknown) => {
              const isEnabled = value as boolean

              return (
                <StatusBadge color={isEnabled ? "green" : "grey"}>
                  {isEnabled ? "Enabled" : "Disabled"}
                </StatusBadge>
              )
            },
          },
        ]}
        data={[
          {
            name: "John",
            is_enabled: true,
          },
          {
            name: "Jane",
            is_enabled: false,
          },
        ]}
        pageSize={2}
        count={2}
        currentPage={currentPage}
        setCurrentPage={setCurrentPage}
      />
    </Container>
  )
}

export const config = defineWidgetConfig({
  zone: "product.details.before",
})

export default ProductWidget
```

This widget also uses the [Container](../container.mdx) 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](../../../js-sdk/page.mdx), create the UI route `src/admin/routes/custom/page.tsx` with the following content:

export const tableExampleHighlights = [
  ["12", "currentPage", "The table's current page."],
  ["13", "limit", "The maximum number of items per page."],
  ["14", "offset", "The number of items to skip before retrieving the current page's items."],
  ["18", "data", "The response fields."],
  ["18", "useQuery", "Fetch products from the Medusa application."]
]

```tsx title="src/admin/routes/custom/page.tsx" collapsibleLines="1-10" expandButtonLabel="Show Imports" highlights={tableExampleHighlights}
import { defineRouteConfig } from "@medusajs/admin-sdk"
import { ChatBubbleLeftRight } from "@medusajs/icons"
import { useQuery } from "@tanstack/react-query"
import { SingleColumnLayout } from "../../layouts/single-column"
import { Table } from "../../components/table"
import { sdk } from "../../lib/config"
import { useMemo, useState } from "react"
import { Container } from "../../components/container"
import { Header } from "../../components/header"

const CustomPage = () => {
  const [currentPage, setCurrentPage] = useState(0)
  const limit = 15
  const offset = useMemo(() => {
    return currentPage * limit
  }, [currentPage])

  const { data } = useQuery({
    queryFn: () => sdk.admin.product.list({
      limit,
      offset,
    }),
    queryKey: [["products", limit, offset]],
  })

  // TODO display table
}

export const config = defineRouteConfig({
  label: "Custom",
  icon: ChatBubbleLeftRight,
})

export 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](https://tanstack.com/query/latest) 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.

<Note title="Tip">

You can change the query to send a request to a custom API route as explained in [this guide](../../../js-sdk/page.mdx#send-requests-to-custom-routes).

</Note>

`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:

```tsx
return (
  <SingleColumnLayout>
    <Container>
      <Header title="Products" />
      {data && (
        <Table
          columns={[
            {
              key: "id",
              label: "ID",
            },
            {
              key: "title",
              label: "Title",
            },
          ]}
          data={data.products as any}
          pageSize={data.limit}
          count={data.count}
          currentPage={currentPage}
          setCurrentPage={setCurrentPage}
        />
      )}
    </Container>
  </SingleColumnLayout>
)
```

Aside from the `Table` component, this UI route also uses the [SingleColumnLayout](../../layouts/single-column/page.mdx), [Container](../container/page.mdx), and [Header](../header/page.mdx) 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.
