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:
+10802
-10623
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>
|
||||
|
||||
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.
|
||||

|
||||
|
||||
<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.
|
||||
|
||||
</Note>
|
||||
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.
|
||||
|
||||
## Example: DataTable with Data Fetching
|
||||
|
||||
@@ -215,11 +213,14 @@ import {
|
||||
// ...
|
||||
useDataTable,
|
||||
} from "@medusajs/ui"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
```
|
||||
|
||||
Then, replace the `TODO` in the component with the following:
|
||||
|
||||
```tsx title="src/admin/routes/custom/page.tsx"
|
||||
const navigate = useNavigate()
|
||||
|
||||
const table = useDataTable({
|
||||
columns,
|
||||
data: data?.products || [],
|
||||
@@ -244,6 +245,10 @@ const table = useDataTable({
|
||||
state: sorting,
|
||||
onSortingChange: setSorting,
|
||||
},
|
||||
onRowClick: (event, row) => {
|
||||
// Handle row click, for example
|
||||
navigate(`/products/${row.id}`)
|
||||
},
|
||||
})
|
||||
|
||||
// TODO render component
|
||||
@@ -251,24 +256,37 @@ const table = useDataTable({
|
||||
|
||||
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.
|
||||
- `data`: The products fetched from the Medusa application.
|
||||
- `getRowId`: A function that returns the unique ID of a row.
|
||||
- `rowCount`: The total number of products that can be retrieved. This is used to determine the number of pages.
|
||||
- `isLoading`: A boolean that indicates if the data is being fetched.
|
||||
- `pagination`: An object to configure pagination. It accepts with the following properties:
|
||||
- `state`: The pagination React state variable.
|
||||
- `onPaginationChange`: A function that updates the pagination state.
|
||||
- `search`: An object to configure searching. It accepts the following properties:
|
||||
- `state`: The search query React state variable.
|
||||
- `onSearchChange`: A function that updates the search query state.
|
||||
- `filtering`: An object to configure filtering. It accepts the following properties:
|
||||
- `state`: The filtering React state variable.
|
||||
- `onFilteringChange`: A function that updates the filtering 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:
|
||||
- `state`: The sorting React state variable.
|
||||
- `onSortingChange`: A function that updates the sorting state.
|
||||
<TypeList
|
||||
types={[
|
||||
{ name: "columns", type: "`array`", description: "The columns to display in the data table. You created this using the `createDataTableColumnHelper` utility." },
|
||||
{ name: "data", type: "`array`", description: "The products fetched from the Medusa application." },
|
||||
{ name: "getRowId", type: "`function`", description: "A function that returns the unique ID of a row." },
|
||||
{ name: "rowCount", type: "`number`", description: "The total number of products that can be retrieved. This is used to determine the number of pages." },
|
||||
{ name: "isLoading", type: "`boolean`", description: "A boolean that indicates if the data is being fetched." },
|
||||
{ name: "pagination", type: "`object`", description: "An object to configure pagination.", children: [
|
||||
{ name: "state", type: "`object`", description: "The pagination React state variable." },
|
||||
{ name: "onPaginationChange", type: "`function`", description: "A function that updates the pagination state." }
|
||||
]},
|
||||
{ name: "search", type: "`object`", description: "An object to configure searching.", children: [
|
||||
{ name: "state", type: "`string`", description: "The search query React state variable." },
|
||||
{ name: "onSearchChange", type: "`function`", description: "A function that updates the search query state." }
|
||||
]},
|
||||
{ name: "filtering", type: "`object`", description: "An object to configure filtering.", children: [
|
||||
{ name: "state", type: "`object`", description: "The filtering React state variable." },
|
||||
{ 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:
|
||||
|
||||
|
||||
@@ -12,13 +12,11 @@ export const metadata = {
|
||||
|
||||
<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>
|
||||
|
||||
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.
|
||||
|
||||
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."],
|
||||
["28", "columnHelper", "Prepare the column creation utility."],
|
||||
["30", "columns", "Define the columns for the data table."],
|
||||
["71", "SubscriptionsPage", "Define the component that shows the UI route's content."],
|
||||
["75", "config", "Export configurations to show the UI route in the sidebar."]
|
||||
["64", "SubscriptionsPage", "Define the component that shows the UI route's content."],
|
||||
["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"
|
||||
@@ -1471,7 +1471,7 @@ import { useMemo, useState } from "react"
|
||||
import { SubscriptionData, SubscriptionStatus } from "../../types"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { sdk } from "../../lib/sdk"
|
||||
import { Link } from "react-router-dom"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
|
||||
const getBadgeColor = (status: SubscriptionStatus) => {
|
||||
switch(status) {
|
||||
@@ -1496,13 +1496,6 @@ const columnHelper = createDataTableColumnHelper<SubscriptionData>()
|
||||
const columns = [
|
||||
columnHelper.accessor("id", {
|
||||
header: "#",
|
||||
cell: ({ getValue }) => {
|
||||
return (
|
||||
<Link to={`/subscriptions/${getValue()}`}>
|
||||
{getValue()}
|
||||
</Link>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("metadata.main_order_id", {
|
||||
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"
|
||||
const SubscriptionsPage = () => {
|
||||
const navigate = useNavigate()
|
||||
const [pagination, setPagination] = useState<DataTablePaginationState>({
|
||||
pageSize: 4,
|
||||
pageIndex: 0,
|
||||
@@ -1588,6 +1582,9 @@ const SubscriptionsPage = () => {
|
||||
state: pagination,
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -1687,7 +1684,7 @@ To test the UI routes, run the Medusa application and go to `http://localhost:90
|
||||
|
||||
After you log-in, you’ll find a new sidebar item “Subscriptions”. Once you click on it, you’ll see the list of subscription purchases.
|
||||
|
||||
To view a subscription’s details, click on its ID, which opens the subscription details page. This page contains the subscription’s orders.
|
||||
To view a subscription’s details, click on its row, which opens the subscription details page. This page contains the subscription’s orders.
|
||||
|
||||
### Further Reads
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ export const generatedEditDates = {
|
||||
"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/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/page.mdx": "2024-07-11T15:56:41+00: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/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/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/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",
|
||||
@@ -5862,7 +5862,7 @@ export const generatedEditDates = {
|
||||
"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/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.OrderAddress/page.mdx": "2025-01-27T11:43:58.773Z",
|
||||
"references/order_models/variables/order_models.OrderChange/page.mdx": "2025-01-27T11:43:58.781Z",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"react-dom": "rc",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark": "^14.0.3",
|
||||
"slugify": "^1.6.6",
|
||||
"tailwind": "*",
|
||||
"tailwindcss": "3.3.3",
|
||||
"typescript": "5.1.6",
|
||||
|
||||
@@ -2,10 +2,11 @@ import { Documentation } from "react-docgen"
|
||||
import { Suspense } from "react"
|
||||
import { Spinner } from "@medusajs/icons"
|
||||
import { PropTable } from "./props-table"
|
||||
import { Container, clx } from "@medusajs/ui"
|
||||
import { Container } from "@medusajs/ui"
|
||||
import { Feedback } from "./feedback"
|
||||
import { MarkdownContent } from "docs-ui"
|
||||
import { H3, MarkdownContent } from "docs-ui"
|
||||
import { components } from "./mdx-components"
|
||||
import slugify from "slugify"
|
||||
|
||||
type ComponentReferenceProps = {
|
||||
mainComponent: string
|
||||
@@ -32,6 +33,11 @@ const ComponentReference = ({
|
||||
)
|
||||
const hasProps =
|
||||
componentSpec?.props && Object.keys(componentSpec.props).length > 0
|
||||
const componentName =
|
||||
componentsToShow.length > 1
|
||||
? componentSpec?.displayName || component
|
||||
: ""
|
||||
const componentSlug = slugify(componentName)
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
@@ -44,9 +50,7 @@ const ComponentReference = ({
|
||||
{componentSpec && (
|
||||
<>
|
||||
{componentsToShow.length > 1 && (
|
||||
<h3 className={clx("h3-docs mb-2 mt-10 text-medusa-fg-base")}>
|
||||
{componentSpec.displayName || component}
|
||||
</h3>
|
||||
<H3 id={componentSlug}>{componentName}</H3>
|
||||
)}
|
||||
{componentSpec.description && (
|
||||
<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:
|
||||
|
||||
- `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
|
||||
|
||||
@@ -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.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
## 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.
|
||||
|
||||
## DataTable with Pagination
|
||||
## Configure Pagination in DataTable
|
||||
|
||||
---
|
||||
|
||||
@@ -357,7 +390,7 @@ return (
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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")),
|
||||
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": {
|
||||
name: "data-table-custom-cell",
|
||||
component: React.lazy(
|
||||
|
||||
@@ -58,7 +58,14 @@ export const useActiveOnScroll = ({
|
||||
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])
|
||||
const setHeadingItems = useCallback(() => {
|
||||
if (!enable) {
|
||||
|
||||
@@ -15490,6 +15490,7 @@ turbo@latest:
|
||||
rehype-slug: ^6.0.0
|
||||
remark: ^14.0.3
|
||||
remark-rehype-plugins: "*"
|
||||
slugify: ^1.6.6
|
||||
tailwind: "*"
|
||||
tailwindcss: 3.3.3
|
||||
ts-node: ^10.9.1
|
||||
|
||||
Reference in New Issue
Block a user