docs: add section on row click in DataTable (#11694)

* docs: add section on row click in DataTable

* update subscriptions example
This commit is contained in:
Shahed Nasser
2025-03-03 18:08:06 +02:00
committed by GitHub
parent ab96ad3b82
commit 92514885e4
12 changed files with 10972 additions and 10678 deletions
File diff suppressed because it is too large Load Diff
@@ -16,15 +16,13 @@ This component is available after [Medusa v2.4.0+](https://github.com/medusajs/m
</Note> </Note>
The [DataTable component in Medusa UI](!ui!/components/data-table) allows you to display data in a table with sorting, filtering, and pagination. The [DataTable component in Medusa UI](!ui!/components/data-table) allows you to display data in a table with sorting, filtering, and pagination. It's used across the Medusa Admin dashboard to showcase a list of items, such as a list of products.
You can use this component in your Admin Extensions to display data in a table format, especially if they're retrieved from API routes of the Medusa application. ![Example of a table in the product listing page](https://res.cloudinary.com/dza7lstvk/image/upload/v1728295658/Medusa%20Resources/list_ddt9zc.png)
<Note> You can use this component in your Admin Extensions to display data in a table format, especially if you're retrieving them from API routes of the Medusa application.
Refer to the [Medusa UI documentation](!ui!/components/data-table) for detailed information about the DataTable component and its different usages. This guide focuses on how to use the `DataTable` component while fetching data from the backend. Refer to the [Medusa UI documentation](!ui!/components/data-table) for detailed information about the DataTable component and its different usages.
</Note>
## Example: DataTable with Data Fetching ## Example: DataTable with Data Fetching
@@ -215,11 +213,14 @@ import {
// ... // ...
useDataTable, useDataTable,
} from "@medusajs/ui" } from "@medusajs/ui"
import { useNavigate } from "react-router-dom"
``` ```
Then, replace the `TODO` in the component with the following: Then, replace the `TODO` in the component with the following:
```tsx title="src/admin/routes/custom/page.tsx" ```tsx title="src/admin/routes/custom/page.tsx"
const navigate = useNavigate()
const table = useDataTable({ const table = useDataTable({
columns, columns,
data: data?.products || [], data: data?.products || [],
@@ -244,6 +245,10 @@ const table = useDataTable({
state: sorting, state: sorting,
onSortingChange: setSorting, onSortingChange: setSorting,
}, },
onRowClick: (event, row) => {
// Handle row click, for example
navigate(`/products/${row.id}`)
},
}) })
// TODO render component // TODO render component
@@ -251,24 +256,37 @@ const table = useDataTable({
The `useDataTable` hook accepts an object with the following properties: The `useDataTable` hook accepts an object with the following properties:
- `columns`: The columns to display in the data table. You created this using the `createDataTableColumnHelper` utility. <TypeList
- `data`: The products fetched from the Medusa application. types={[
- `getRowId`: A function that returns the unique ID of a row. { name: "columns", type: "`array`", description: "The columns to display in the data table. You created this using the `createDataTableColumnHelper` utility." },
- `rowCount`: The total number of products that can be retrieved. This is used to determine the number of pages. { name: "data", type: "`array`", description: "The products fetched from the Medusa application." },
- `isLoading`: A boolean that indicates if the data is being fetched. { name: "getRowId", type: "`function`", description: "A function that returns the unique ID of a row." },
- `pagination`: An object to configure pagination. It accepts with the following properties: { name: "rowCount", type: "`number`", description: "The total number of products that can be retrieved. This is used to determine the number of pages." },
- `state`: The pagination React state variable. { name: "isLoading", type: "`boolean`", description: "A boolean that indicates if the data is being fetched." },
- `onPaginationChange`: A function that updates the pagination state. { name: "pagination", type: "`object`", description: "An object to configure pagination.", children: [
- `search`: An object to configure searching. It accepts the following properties: { name: "state", type: "`object`", description: "The pagination React state variable." },
- `state`: The search query React state variable. { name: "onPaginationChange", type: "`function`", description: "A function that updates the pagination state." }
- `onSearchChange`: A function that updates the search query state. ]},
- `filtering`: An object to configure filtering. It accepts the following properties: { name: "search", type: "`object`", description: "An object to configure searching.", children: [
- `state`: The filtering React state variable. { name: "state", type: "`string`", description: "The search query React state variable." },
- `onFilteringChange`: A function that updates the filtering state. { name: "onSearchChange", type: "`function`", description: "A function that updates the search query state." }
- `filters`: The filters to display in the data table. You created this using the `createDataTableFilterHelper` utility. ]},
- `sorting`: An object to configure sorting. It accepts the following properties: { name: "filtering", type: "`object`", description: "An object to configure filtering.", children: [
- `state`: The sorting React state variable. { name: "state", type: "`object`", description: "The filtering React state variable." },
- `onSortingChange`: A function that updates the sorting state. { name: "onFilteringChange", type: "`function`", description: "A function that updates the filtering state." }
]},
{ name: "filters", type: "`array`", description: "The filters to display in the data table. You created this using the `createDataTableFilterHelper` utility." },
{ name: "sorting", type: "`object`", description: "An object to configure sorting.", children: [
{ name: "state", type: "`object`", description: "The sorting React state variable." },
{ name: "onSortingChange", type: "`function`", description: "A function that updates the sorting state." }
]},
{ name: "onRowClick", type: "`function`", description: "A function that allows you to perform an action when the user clicks on a row. In this example, you navigate to the product's detail page.", children: [
{ name: "event", type: "`mouseevent`", description: "An instance of the [MouseClickEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) object." },
{ name: "row", type: "`object`", description: "The data of the row that was clicked." }
]}
]}
sectionTitle="Example: DataTable with Data Fetching"
/>
Finally, you'll render the data table. But first, add the following imports at the top of the page: Finally, you'll render the data table. But first, add the following imports at the top of the page:
@@ -12,13 +12,11 @@ export const metadata = {
<Note> <Note>
If you're using [Medusa v2.4.0+](https://github.com/medusajs/medusa/releases/tag/v2.4.0), it's recommended to use the [Data Table](../data-table/page.mdx) component instead. If you're using [Medusa v2.4.0+](https://github.com/medusajs/medusa/releases/tag/v2.4.0), it's recommended to use the [Data Table](../data-table/page.mdx) component instead as it provides features for sorting, filtering, pagination, and more with a simpler API.
</Note> </Note>
The listing pages in the Admin show a table with pagination. You can use the `Table` component from Medusa UI to display data in a table. It's mostly recommended for simpler tables.
![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: To create a component that shows a table with pagination, create the file `src/admin/components/table.tsx` with the following content:
@@ -1459,8 +1459,8 @@ export const list1Highlights = [
["23", "getStatusTitle", "Capitalize the status for the text shown in the status's badge."], ["23", "getStatusTitle", "Capitalize the status for the text shown in the status's badge."],
["28", "columnHelper", "Prepare the column creation utility."], ["28", "columnHelper", "Prepare the column creation utility."],
["30", "columns", "Define the columns for the data table."], ["30", "columns", "Define the columns for the data table."],
["71", "SubscriptionsPage", "Define the component that shows the UI route's content."], ["64", "SubscriptionsPage", "Define the component that shows the UI route's content."],
["75", "config", "Export configurations to show the UI route in the sidebar."] ["68", "config", "Export configurations to show the UI route in the sidebar."]
] ]
```tsx title="src/admin/routes/subscriptions/page.tsx" highlights={list1Highlights} collapsibleLines="1-9" expandMoreLabel="Show Imports" ```tsx title="src/admin/routes/subscriptions/page.tsx" highlights={list1Highlights} collapsibleLines="1-9" expandMoreLabel="Show Imports"
@@ -1471,7 +1471,7 @@ import { useMemo, useState } from "react"
import { SubscriptionData, SubscriptionStatus } from "../../types" import { SubscriptionData, SubscriptionStatus } from "../../types"
import { useQuery } from "@tanstack/react-query" import { useQuery } from "@tanstack/react-query"
import { sdk } from "../../lib/sdk" import { sdk } from "../../lib/sdk"
import { Link } from "react-router-dom" import { useNavigate } from "react-router-dom"
const getBadgeColor = (status: SubscriptionStatus) => { const getBadgeColor = (status: SubscriptionStatus) => {
switch(status) { switch(status) {
@@ -1496,13 +1496,6 @@ const columnHelper = createDataTableColumnHelper<SubscriptionData>()
const columns = [ const columns = [
columnHelper.accessor("id", { columnHelper.accessor("id", {
header: "#", header: "#",
cell: ({ getValue }) => {
return (
<Link to={`/subscriptions/${getValue()}`}>
{getValue()}
</Link>
)
},
}), }),
columnHelper.accessor("metadata.main_order_id", { columnHelper.accessor("metadata.main_order_id", {
header: "Main Order", header: "Main Order",
@@ -1558,6 +1551,7 @@ In the `SubscriptionsPage` component, you'll fetch the subscriptions and show th
```tsx title="src/admin/routes/subscriptions/page.tsx" ```tsx title="src/admin/routes/subscriptions/page.tsx"
const SubscriptionsPage = () => { const SubscriptionsPage = () => {
const navigate = useNavigate()
const [pagination, setPagination] = useState<DataTablePaginationState>({ const [pagination, setPagination] = useState<DataTablePaginationState>({
pageSize: 4, pageSize: 4,
pageIndex: 0, pageIndex: 0,
@@ -1588,6 +1582,9 @@ const SubscriptionsPage = () => {
state: pagination, state: pagination,
onPaginationChange: setPagination, onPaginationChange: setPagination,
}, },
onRowClick(event, row) {
navigate(`/subscriptions/${row.id}`)
},
}) })
@@ -1610,7 +1607,7 @@ In the component, you first initialize a `pagination` state variable of type `Da
Then, you use the `useQuery` hook from the `@tanstack/react-query` package to fetch the subscriptions. In the query function, you use the JS SDK to send a request to the `/admin/subscriptions` API route with the pagination query parameters. Then, you use the `useQuery` hook from the `@tanstack/react-query` package to fetch the subscriptions. In the query function, you use the JS SDK to send a request to the `/admin/subscriptions` API route with the pagination query parameters.
Next, you use the `useDataTable` hook to create a data table instance. You pass the columns, subscriptions data, row count, loading state, and pagination settings to the hook. Next, you use the `useDataTable` hook to create a data table instance. You pass the columns, subscriptions data, row count, loading state, and pagination settings to the hook. You also navigate to the subscription details page when a row is clicked.
Finally, you render the data table with the subscriptions data, along with the pagination controls. Finally, you render the data table with the subscriptions data, along with the pagination controls.
@@ -1687,7 +1684,7 @@ To test the UI routes, run the Medusa application and go to `http://localhost:90
After you log-in, youll find a new sidebar item “Subscriptions”. Once you click on it, youll see the list of subscription purchases. After you log-in, youll find a new sidebar item “Subscriptions”. Once you click on it, youll see the list of subscription purchases.
To view a subscriptions details, click on its ID, which opens the subscription details page. This page contains the subscriptions orders. To view a subscriptions details, click on its row, which opens the subscription details page. This page contains the subscriptions orders.
### Further Reads ### Further Reads
+3 -3
View File
@@ -122,7 +122,7 @@ export const generatedEditDates = {
"app/recipes/oms/page.mdx": "2025-02-26T12:41:00.030Z", "app/recipes/oms/page.mdx": "2025-02-26T12:41:00.030Z",
"app/recipes/personalized-products/page.mdx": "2025-02-26T12:41:48.547Z", "app/recipes/personalized-products/page.mdx": "2025-02-26T12:41:48.547Z",
"app/recipes/pos/page.mdx": "2025-02-26T12:42:52.949Z", "app/recipes/pos/page.mdx": "2025-02-26T12:42:52.949Z",
"app/recipes/subscriptions/examples/standard/page.mdx": "2025-02-27T13:43:37.201Z", "app/recipes/subscriptions/examples/standard/page.mdx": "2025-03-03T15:02:56.854Z",
"app/recipes/subscriptions/page.mdx": "2025-02-26T12:31:49.933Z", "app/recipes/subscriptions/page.mdx": "2025-02-26T12:31:49.933Z",
"app/recipes/page.mdx": "2024-07-11T15:56:41+00:00", "app/recipes/page.mdx": "2024-07-11T15:56:41+00:00",
"app/service-factory-reference/methods/create/page.mdx": "2024-07-31T17:01:33+03:00", "app/service-factory-reference/methods/create/page.mdx": "2024-07-31T17:01:33+03:00",
@@ -2102,7 +2102,7 @@ export const generatedEditDates = {
"app/admin-components/components/header/page.mdx": "2024-10-07T11:16:47.407Z", "app/admin-components/components/header/page.mdx": "2024-10-07T11:16:47.407Z",
"app/admin-components/components/json-view-section/page.mdx": "2024-10-07T11:15:58.833Z", "app/admin-components/components/json-view-section/page.mdx": "2024-10-07T11:15:58.833Z",
"app/admin-components/components/section-row/page.mdx": "2024-10-07T11:15:58.832Z", "app/admin-components/components/section-row/page.mdx": "2024-10-07T11:15:58.832Z",
"app/admin-components/components/table/page.mdx": "2025-01-22T16:00:56.772Z", "app/admin-components/components/table/page.mdx": "2025-03-03T14:53:37.952Z",
"app/admin-components/page.mdx": "2024-10-07T11:09:49.493Z", "app/admin-components/page.mdx": "2024-10-07T11:09:49.493Z",
"app/admin-components/layouts/single-column/page.mdx": "2024-10-07T11:16:06.435Z", "app/admin-components/layouts/single-column/page.mdx": "2024-10-07T11:16:06.435Z",
"app/admin-components/layouts/two-column/page.mdx": "2024-10-07T11:16:10.092Z", "app/admin-components/layouts/two-column/page.mdx": "2024-10-07T11:16:10.092Z",
@@ -5862,7 +5862,7 @@ export const generatedEditDates = {
"references/core_flows/types/core_flows.ValidateRefundStepInput/page.mdx": "2025-02-24T10:48:34.159Z", "references/core_flows/types/core_flows.ValidateRefundStepInput/page.mdx": "2025-02-24T10:48:34.159Z",
"app/plugins/guides/wishlist/page.mdx": "2025-02-20T17:13:30.276Z", "app/plugins/guides/wishlist/page.mdx": "2025-02-20T17:13:30.276Z",
"app/plugins/page.mdx": "2025-02-26T11:39:25.709Z", "app/plugins/page.mdx": "2025-02-26T11:39:25.709Z",
"app/admin-components/components/data-table/page.mdx": "2025-01-22T16:01:01.279Z", "app/admin-components/components/data-table/page.mdx": "2025-03-03T14:55:58.556Z",
"references/order_models/variables/order_models.Order/page.mdx": "2025-01-27T11:43:58.788Z", "references/order_models/variables/order_models.Order/page.mdx": "2025-01-27T11:43:58.788Z",
"references/order_models/variables/order_models.OrderAddress/page.mdx": "2025-01-27T11:43:58.773Z", "references/order_models/variables/order_models.OrderAddress/page.mdx": "2025-01-27T11:43:58.773Z",
"references/order_models/variables/order_models.OrderChange/page.mdx": "2025-01-27T11:43:58.781Z", "references/order_models/variables/order_models.OrderChange/page.mdx": "2025-01-27T11:43:58.781Z",
+1
View File
@@ -34,6 +34,7 @@
"react-dom": "rc", "react-dom": "rc",
"rehype-slug": "^6.0.0", "rehype-slug": "^6.0.0",
"remark": "^14.0.3", "remark": "^14.0.3",
"slugify": "^1.6.6",
"tailwind": "*", "tailwind": "*",
"tailwindcss": "3.3.3", "tailwindcss": "3.3.3",
"typescript": "5.1.6", "typescript": "5.1.6",
@@ -2,10 +2,11 @@ import { Documentation } from "react-docgen"
import { Suspense } from "react" import { Suspense } from "react"
import { Spinner } from "@medusajs/icons" import { Spinner } from "@medusajs/icons"
import { PropTable } from "./props-table" import { PropTable } from "./props-table"
import { Container, clx } from "@medusajs/ui" import { Container } from "@medusajs/ui"
import { Feedback } from "./feedback" import { Feedback } from "./feedback"
import { MarkdownContent } from "docs-ui" import { H3, MarkdownContent } from "docs-ui"
import { components } from "./mdx-components" import { components } from "./mdx-components"
import slugify from "slugify"
type ComponentReferenceProps = { type ComponentReferenceProps = {
mainComponent: string mainComponent: string
@@ -32,6 +33,11 @@ const ComponentReference = ({
) )
const hasProps = const hasProps =
componentSpec?.props && Object.keys(componentSpec.props).length > 0 componentSpec?.props && Object.keys(componentSpec.props).length > 0
const componentName =
componentsToShow.length > 1
? componentSpec?.displayName || component
: ""
const componentSlug = slugify(componentName)
return ( return (
<Suspense <Suspense
fallback={ fallback={
@@ -44,9 +50,7 @@ const ComponentReference = ({
{componentSpec && ( {componentSpec && (
<> <>
{componentsToShow.length > 1 && ( {componentsToShow.length > 1 && (
<h3 className={clx("h3-docs mb-2 mt-10 text-medusa-fg-base")}> <H3 id={componentSlug}>{componentName}</H3>
{componentSpec.displayName || component}
</h3>
)} )}
{componentSpec.description && ( {componentSpec.description && (
<MarkdownContent components={components}> <MarkdownContent components={components}>
@@ -66,7 +66,7 @@ The `createDataTableColumnHelper` utility is a function that returns a helper us
For each column in the table, use the `accessor` method of the column helper to specify configurations for a specific column. The `accessor` method accepts the column's key in the table's data as the first parameter, and an object with the following properties as the second parameter: For each column in the table, use the `accessor` method of the column helper to specify configurations for a specific column. The `accessor` method accepts the column's key in the table's data as the first parameter, and an object with the following properties as the second parameter:
- `header`: The table header text for the column. - `header`: The table header text for the column.
- `enableSorting`: (optional) A boolean that indicates whether data in the table can be sorted by this column. More on sorting in [this section](#datatable-with-sorting). - `enableSorting`: (optional) A boolean that indicates whether data in the table can be sorted by this column. More on sorting in [this section](#configure-sorting-in-datatable).
### Create Table Instance ### Create Table Instance
@@ -146,6 +146,39 @@ Refer to the examples later on this page to learn how to add pagination, filteri
Refer to [this Admin Components guide](https://docs.medusajs.com/resources/admin-components/components/data-table) for an example on using the `DataTable` component with data fetching from the Medusa application. Refer to [this Admin Components guide](https://docs.medusajs.com/resources/admin-components/components/data-table) for an example on using the `DataTable` component with data fetching from the Medusa application.
## Handle Row Click
---
<ComponentExample name="data-table-row-click" disableCenterAlignPreview />
In many cases, you want to perform an action when a row is clicked. Most commonly, you may want to open the details page of the row when it's clicked.
<Note>
For bulk actions, such as deleting multiple rows, use the [Command Bar](#perform-bulk-actions-on-datatable-rows) instead.
</Note>
The `useDataTable` hook accepts an `onRowClick` property that you can use to handle row clicks:
```tsx
const navigate = useNavigate()
const table = useDataTable({
// ...
onRowClick(event, row) {
navigate(`/author/${row.id}`)
},
})
```
The value of `onRowClick` is a function that accepts two parameters:
- `event`: An instance of the [MouseClickEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) object.
- `row`: The data of the row that was clicked.
In the above example, you use a `navigate` function, retrieved through the `useNavigate` hook from `react-router-dom`, to navigate to the details page of the row that was clicked.
## Configure Cell Rendering ## Configure Cell Rendering
@@ -193,7 +226,7 @@ const columns = [
The `cell` property's value is a function that returns a string or a React node to be rendered in the cell. The function receives as a parameter an object having a `getValue` property to get the raw value of the cell. The `cell` property's value is a function that returns a string or a React node to be rendered in the cell. The function receives as a parameter an object having a `getValue` property to get the raw value of the cell.
## DataTable with Search ## Configure Search in DataTable
--- ---
@@ -261,7 +294,7 @@ return (
This will show a search input at the top of the table, in the data table's toolbar. This will show a search input at the top of the table, in the data table's toolbar.
## DataTable with Pagination ## Configure Pagination in DataTable
--- ---
@@ -357,7 +390,7 @@ return (
This will show the pagination controls at the end of the table. This will show the pagination controls at the end of the table.
## DataTable with Filters ## Configure Filters in DataTable
--- ---
@@ -626,7 +659,7 @@ const [filtering, setFiltering] = useState<DataTableFilteringState>({
The user can still change the filter values, but the initial values will be applied when the table is first rendered. The user can still change the filter values, but the initial values will be applied when the table is first rendered.
## DataTable with Sorting ## Configure Sorting in DataTable
--- ---
@@ -767,7 +800,7 @@ const [sorting, setSorting] = useState<DataTableSortingState | null>({
The user can still change the sort values, but the initial values will be applied when the table is first rendered. The user can still change the sort values, but the initial values will be applied when the table is first rendered.
## DataTable with Command Bar ## Perform Bulk Actions on DataTable Rows
--- ---
@@ -0,0 +1,49 @@
import { createDataTableColumnHelper, useDataTable, DataTable, Heading } from "@medusajs/ui"
const products = [
{
id: "1",
title: "Shirt",
price: 10
},
{
id: "2",
title: "Pants",
price: 20
}
]
const columnHelper = createDataTableColumnHelper<typeof products[0]>()
const columns = [
columnHelper.accessor("title", {
header: "Title",
enableSorting: true,
}),
columnHelper.accessor("price", {
header: "Price",
enableSorting: true,
}),
]
export default function ProductTable () {
const table = useDataTable({
columns,
data: products,
getRowId: (product) => product.id,
rowCount: products.length,
isLoading: false,
onRowClick: (event, row) => {
alert(`You clicked row #${row.id}`)
}
})
return (
<DataTable instance={table}>
<DataTable.Toolbar className="flex flex-col items-start justify-between gap-2 md:flex-row md:items-center">
<Heading>Products</Heading>
</DataTable.Toolbar>
<DataTable.Table />
</DataTable>
)
}
@@ -92,6 +92,13 @@ export const ExampleRegistry: ExampleRegistryType = {
component: React.lazy(async () => import("@/examples/data-table-demo")), component: React.lazy(async () => import("@/examples/data-table-demo")),
file: "src/examples/data-table-demo.tsx", file: "src/examples/data-table-demo.tsx",
}, },
"data-table-row-click": {
name: "data-table-row-click",
component: React.lazy(
async () => import("@/examples/data-table-row-click")
),
file: "src/examples/data-table-row-click.tsx",
},
"data-table-custom-cell": { "data-table-custom-cell": {
name: "data-table-custom-cell", name: "data-table-custom-cell",
component: React.lazy( component: React.lazy(
@@ -58,7 +58,14 @@ export const useActiveOnScroll = ({
return [] return []
} }
return root?.querySelectorAll(querySelector) const filteredHeadings: Element[] = []
root?.querySelectorAll(querySelector).forEach((heading) => {
if (heading.id) {
filteredHeadings.push(heading)
}
})
return filteredHeadings
}, [isBrowser, pathname, root, enable]) }, [isBrowser, pathname, root, enable])
const setHeadingItems = useCallback(() => { const setHeadingItems = useCallback(() => {
if (!enable) { if (!enable) {
+1
View File
@@ -15490,6 +15490,7 @@ turbo@latest:
rehype-slug: ^6.0.0 rehype-slug: ^6.0.0
remark: ^14.0.3 remark: ^14.0.3
remark-rehype-plugins: "*" remark-rehype-plugins: "*"
slugify: ^1.6.6
tailwind: "*" tailwind: "*"
tailwindcss: 3.3.3 tailwindcss: 3.3.3
ts-node: ^10.9.1 ts-node: ^10.9.1