DataGrid - Admin Components
The DataGrid component shows an editable spreadsheet-like grid whose values are bound to a react-hook-form form. The Medusa Admin uses it to bulk-edit variant prices and inventory quantities.
The grid supports keyboard navigation, range selection, copying and pasting across cells, and undo and redo.
Example#
Create the grid's columns with createDataGridHelper, then pass your form to the grid's state prop:
import {
DataGrid,
createDataGridHelper,
} from "@medusajs/dashboard/components"
import { useForm } from "react-hook-form"
const columnHelper = createDataGridHelper<
Variant,
VariantsFormValues
>()
const VariantsPage = ({ variants }) => {
const form = useForm<VariantsFormValues>({
defaultValues: { variants },
})
const columns = [
columnHelper.column({
id: "title",
name: "Title",
header: "Title",
cell: (context) => (
<DataGrid.ReadonlyCell context={context}>
{context.row.original.title}
</DataGrid.ReadonlyCell>
),
}),
columnHelper.column({
id: "sku",
name: "SKU",
header: "SKU",
type: "text",
field: (context) =>
`variants.${context.row.index}.sku`,
cell: (context) => (
<DataGrid.TextCell context={context} />
),
}),
]
return (
<DataGrid
columns={columns}
data={variants}
state={form}
/>
)
}
export default VariantsPage
The field function returns the path of the form field that the cell edits. If it returns null, the cell isn't editable.
Type Arguments#
DataGrid accepts two type arguments:
TData: the type of a single row in the grid.TFieldValues: the shape of the values in the react-hook-form form that the grid edits. It defaults toFieldValues.
You rarely set them explicitly. TypeScript infers TData from the columns and data props, and TFieldValues from the state prop:
import { useForm } from "react-hook-form"
import { HttpTypes } from "@medusajs/framework/types"
type PricesForm = {
variants: Record<string, Record<string, number>>
}
const form = useForm<PricesForm>()
Passing that form as state sets TFieldValues to PricesForm, and typing the columns sets TData:
createDataGridHelper takes the same two type arguments, so set them there and the grid infers the rest:
To set the type arguments explicitly, pass them when you use the component:
<DataGrid<HttpTypes.AdminProductVariant, PricesForm>
columns={columns}
data={variants}
state={form}
/>
Props#
DataGrid accepts the DataGridProps type, which extends DataGridRootProps:
columnsColumnDef<TData>[]createDataGridHelper or createDataGridPriceColumns, which also set the TData and TFieldValues type arguments.dataTData[]OptionalTData is the row type.stateUseFormReturn<TFieldValues>TFieldValues is the form's value type.isLoadingbooleanOptionalgetSubRows(row: TData) => TData[] | undefinedOptionalTData is the row type.onEditingChange(isEditing: boolean) => voidOptionaldisableInteractionsbooleanOptionalmultiColumnSelectionbooleanOptionalshowColumnsDropdownbooleanOptionalheaderContentReactNodeOptionaltotalRowCountnumberOptionalonFetchMore() => voidOptionalisFetchingMorebooleanOptionalhasNextPagebooleanOptionalCell Components#
DataGrid has the following sub-components to render a column's cells. Each of them accepts a context prop, which is the cell context that the column's cell function receives:
Component | Description |
|---|---|
| Edits the cell's value in a single-line text input. |
| Edits the cell's value in a multi-line text input. |
| Edits long text. The user can expand the cell into a modal. |
| Edits the cell's value in a number input. It also accepts the |
| Edits a price. It also accepts a |
| Edits the cell's value with a checkbox. It also accepts a |
| Shows its |
createDataGridHelper#
createDataGridHelper returns a typed helper whose column method defines a grid column. It accepts two type parameters: the type of a row, and the type of the form's values.
For example, the following column shows a variant's title in an editable text cell:
columnHelper.column({
id: "title",
name: "Title",
header: "Title",
field: (context) =>
`variants.${context.row.index}.title`,
type: "text",
cell: (context) => (
<DataGrid.TextCell context={context} />
),
})
The header and cell properties accept either a string or a function. The function receives a context object that you pass to the cell component, which uses it to read and write the cell's value.
The column method accepts an object with the following properties:
idstringheaderstring | (context) => ReactNodeThe column's header. Pass a string to show it as plain text, or a function to render custom content.
headerstring | (context) => ReactNodecellstring | (context) => ReactNodeThe cell's content. Pass a function that returns one of the cell components, and pass the function's context to that component so it can read and edit the cell's value.
cellstring | (context) => ReactNodenamestringOptionalfield(context: FieldContext<TData>) => FieldPath<TFieldValues> | nullOptionalReturns the path of the form field that the cell edits, where TData and TFieldValues are the type arguments. Return null, or omit the property, to make the cell read-only. Pass type with it.
field(context: FieldContext<TData>) => FieldPath<TFieldValues> | nullOptionaltype"text" | "multiline-text" | "number" | "boolean" | "togglable-number"Optionalfield, and not allowed otherwise.disableHidingbooleanOptionalDefault: false
sizenumberOptionalminSizenumberOptionalmaxSizenumberOptionalcreateDataGridPriceColumns#
createDataGridPriceColumns returns a price column for each of the passed currencies and regions. Use it to let the user edit prices for many currencies and regions at once.
import {
createDataGridPriceColumns,
} from "@medusajs/dashboard/components"
import { useTranslation } from "react-i18next"
const { t } = useTranslation()
const priceColumns = createDataGridPriceColumns<
Variant,
VariantsFormValues
>({
currencies: ["usd", "eur"],
regions,
pricePreferences,
getFieldName: (context, value) =>
`variants.${context.row.index}.prices.${value}`,
t,
})
It accepts an object with the following properties:
getFieldName(context: FieldContext<TData>, value: string) => FieldPath<TFieldValues> | nullReturns the path of the form field that a price cell edits, where TData and TFieldValues are the type arguments. value is the currency's code or the region's ID.
getFieldName(context: FieldContext<TData>, value: string) => FieldPath<TFieldValues> | nulltTFunctionuseTranslation of react-i18next, used for the columns' names.currenciesstring[]Optionalcurrency_prices.{code}.regionsAdminRegion[]Optionalregion_prices.{id}.pricePreferencesAdminPricePreference[]OptionalisReadyOnly(context: FieldContext<TData>) => booleanOptionalReturns whether a cell is read-only, where TData is the row type. The function renders a read-only cell when it returns true.
isReadyOnly(context: FieldContext<TData>) => booleanOptionalrenderPriceCell(code: string, context: CellContext<TData, any>) => ReactNodeOptionalRenders a custom price cell, where TData is the row type. If you don't pass it, the function renders a DataGrid.CurrencyCell.
renderPriceCell(code: string, context: CellContext<TData, any>) => ReactNodeOptionalRelated Components#
- Data Table: a read-only table for a list of records.