# Source: https://mui.com/x/react-data-grid/column-recipes.md

---
title: Data Grid - Column recipes
---

# Data Grid - Column customization recipes

Advanced column customization recipes.

## Persisting column width and order

When the `columns` prop reference is updated, the column width and order is reset to the `colDef.width` and the order of the `colDef` object and any updates will be lost.
This is because the Data Grid considers update of the columns prop as a new set of columns, and the previous state is discarded.

To persist the column width and order when the `columns` prop is updated, consider persisting the state of the columns in the userland.

```tsx
import * as React from 'react';
import {
  DataGridPro,
  useGridApiRef,
  GridColDef,
  GridColumnResizeParams,
  gridColumnFieldsSelector,
  GridApiPro,
} from '@mui/x-data-grid-pro';
import Button from '@mui/material/Button';

const rows = [
  {
    id: 1,
    username: '@MUI',
    age: 20,
  },
];

export default function ColumnSizingPersistWidthOrder() {
  const apiRef = useGridApiRef();
  const [index, setIndex] = React.useState(0);
  const inputColumns = React.useMemo(
    () => [
      { field: 'id' },
      { field: 'username', width: 200, key: index },
      { field: 'age', disableReorder: true },
    ],
    [index],
  );

  const columnsState = useColumnsState(apiRef, inputColumns);

  return (
    <div style={{ width: '100%' }}>
      <Button onClick={() => setIndex((prev) => prev + 1)}>
        Update columns reference
      </Button>
      <div style={{ height: 250 }}>
        <DataGridPro
          apiRef={apiRef}
          columns={columnsState.columns}
          onColumnWidthChange={columnsState.onColumnWidthChange}
          onColumnOrderChange={columnsState.onColumnOrderChange}
          rows={rows}
        />
      </div>
    </div>
  );
}

const useColumnsState = (
  apiRef: React.RefObject<GridApiPro | null>,
  columns: GridColDef[],
) => {
  const [widths, setWidths] = React.useState<Record<GridColDef['field'], number>>(
    {},
  );
  const [orderedFields, setOrderedFields] = React.useState<GridColDef['field'][]>(
    () => columns.map((column) => column.field),
  );

  const onColumnWidthChange = React.useCallback(
    ({ colDef, width }: GridColumnResizeParams) => {
      setWidths((prev) => ({ ...prev, [colDef.field]: width }));
    },
    [setWidths],
  );

  const onColumnOrderChange = React.useCallback(() => {
    setOrderedFields(gridColumnFieldsSelector(apiRef));
  }, [apiRef, setOrderedFields]);

  const computedColumns = React.useMemo(
    () =>
      orderedFields.reduce<GridColDef[]>((acc, field) => {
        const column = columns.find((col) => col.field === field);
        if (!column) {
          return acc;
        }
        if (widths[field]) {
          acc.push({
            ...column,
            width: widths[field],
          });
          return acc;
        }
        acc.push(column);
        return acc;
      }, []),
    [columns, widths, orderedFields],
  );

  return { columns: computedColumns, onColumnWidthChange, onColumnOrderChange };
};

```

:::warning
[Column ordering](/x/react-data-grid/column-ordering/) is a Pro feature, to use it you must be on a Pro or Premium plan.
:::

### With pivoting [<span class="plan-premium"></span>](/x/introduction/licensing/#premium-plan 'Premium plan')

Pivoting creates columns dynamically, based on the pivoting model and the row data.

To keep the column width of the pivoted grid after `columns` reference is changed, use [`groupingColDef`](/x/api/data-grid/data-grid-premium/#data-grid-premium-prop-groupingColDef) and [`pivotingColDef`](/x/api/data-grid/data-grid-premium/#data-grid-premium-prop-pivotingColDef) props to apply the width updates to the autogenerated columns.

The demo below uses modified version of the `useColumnsState()` from the previous example in combination with a callbacks for grouping and pivoting columns.

```tsx
import * as React from 'react';
import {
  DataGridPremium,
  useGridApiRef,
  GridColDef,
  GridColumnResizeParams,
  GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD,
} from '@mui/x-data-grid-premium';
import Button from '@mui/material/Button';

const rows = [
  {
    id: 1,
    product: 'Apples',
    region: 'North',
    quarter: 'Q1',
    sales: 1000,
    profit: 100,
    size: 'L',
  },
  {
    id: 2,
    product: 'Apples',
    region: 'South',
    quarter: 'Q1',
    sales: 1200,
    profit: 120,
    size: 'M',
  },
  {
    id: 3,
    product: 'Oranges',
    region: 'North',
    quarter: 'Q1',
    sales: 800,
    profit: 80,
    size: 'M',
  },
  {
    id: 4,
    product: 'Oranges',
    region: 'South',
    quarter: 'Q1',
    sales: 900,
    profit: 90,
    size: 'S',
  },
  {
    id: 5,
    product: 'Apples',
    region: 'North',
    quarter: 'Q2',
    sales: 1100,
    profit: 110,
    size: 'L',
  },
  {
    id: 6,
    product: 'Apples',
    region: 'South',
    quarter: 'Q2',
    sales: 1300,
    profit: 130,
    size: 'L',
  },
  {
    id: 7,
    product: 'Oranges',
    region: 'North',
    quarter: 'Q2',
    sales: 850,
    profit: 85,
    size: 'M',
  },
  {
    id: 8,
    product: 'Oranges',
    region: 'South',
    quarter: 'Q2',
    sales: 950,
    profit: 95,
    size: 'S',
  },
];

const pivotModel = {
  rows: [{ field: 'product' }, { field: 'size' }],
  columns: [{ field: 'region' }, { field: 'quarter' }],
  values: [
    { field: 'sales', aggFunc: 'sum' },
    { field: 'profit', aggFunc: 'avg' },
  ],
};

export default function ColumnSizingPersistWidthPivoting() {
  const apiRef = useGridApiRef();
  const [index, setIndex] = React.useState(0);
  const inputColumns: GridColDef[] = React.useMemo(
    () => [
      { field: 'product', headerName: 'Product', key: index },
      { field: 'region', headerName: 'Region' },
      { field: 'quarter', headerName: 'Quarter' },
      {
        field: 'sales',
        headerName: 'Sales',
        type: 'number',
      },
      {
        field: 'profit',
        headerName: 'Profit',
        type: 'number',
        valueFormatter: (value: number) => {
          if (!value) {
            return '';
          }
          return `${value}%`;
        },
      },
      {
        field: 'size',
        headerName: 'Size',
      },
    ],
    [index],
  );

  const columnsState = useColumnsState(inputColumns);

  const pivotingColDef = React.useCallback(
    (field: GridColDef['field'], group: string[]) => {
      const pivotingField = [...group, field].join('>->');
      const fieldWidth = columnsState.getColumnWidth(pivotingField);
      return fieldWidth ? { width: fieldWidth } : {};
    },
    [columnsState],
  );

  const groupingColDef = React.useCallback(() => {
    const column = apiRef.current?.getColumn(
      GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD,
    );
    return column?.width ? { width: column.width } : {};
  }, [apiRef]);

  return (
    <div style={{ width: '100%' }}>
      <Button onClick={() => setIndex((prev) => prev + 1)}>
        Update columns reference
      </Button>
      <div style={{ height: 450 }}>
        <DataGridPremium
          apiRef={apiRef}
          columns={columnsState.columns}
          onColumnWidthChange={columnsState.onColumnWidthChange}
          rows={rows}
          initialState={{
            pivoting: {
              enabled: true,
              model: pivotModel,
            },
          }}
          pivotingColDef={pivotingColDef}
          groupingColDef={groupingColDef}
        />
      </div>
    </div>
  );
}

const useColumnsState = (columns: GridColDef[]) => {
  const [widths, setWidths] = React.useState<Record<GridColDef['field'], number>>(
    {},
  );

  const onColumnWidthChange = React.useCallback(
    ({ colDef, width }: GridColumnResizeParams) => {
      setWidths((prev) => ({ ...prev, [colDef.field]: width }));
    },
    [setWidths],
  );

  const getColumnWidth = React.useCallback(
    (field: string): number | undefined => widths[field],
    [widths],
  );

  const computedColumns = React.useMemo(
    () =>
      columns.reduce<GridColDef[]>((acc, column) => {
        if (widths[column.field]) {
          acc.push({
            ...column,
            width: widths[column.field],
          });
          return acc;
        }
        acc.push(column);
        return acc;
      }, []),
    [columns, widths],
  );

  return {
    columns: computedColumns,
    getColumnWidth,
    onColumnWidthChange,
  };
};

```

## Custom long text popup content

The default rendered cell of the `longText` column type uses `GridLongTextCell` component to show the popup with the full text content when the expand button is clicked. To customize the popup content, use the `renderContent` prop of the `GridLongTextCell` component.

The example below demonstrates two customizations:

- **Character count**: Shows character count below the text
- **Markdown preview**: Renders markdown content as formatted HTML

```tsx
import * as React from 'react';
import Box from '@mui/material/Box';
import { DataGrid, GridColDef, GridLongTextCell } from '@mui/x-data-grid';
import { styled } from '@mui/material/styles';
import Markdown from 'markdown-to-jsx';

const rows = [
  {
    id: 1,
    title: 'Introduction to React',
    notes:
      'React is a JavaScript library for building user interfaces. It uses a component-based architecture and virtual DOM for efficient updates.',
    markdown:
      '# Getting Started\n\nTo create a new React app:\n\n```bash\nnpx create-react-app my-app\n```\n\n**Key concepts:**\n- Components\n- Props\n- State',
  },
  {
    id: 2,
    title: 'TypeScript Basics',
    notes:
      'TypeScript adds static type definitions to JavaScript. It helps catch errors early and improves code maintainability in large codebases.',
    markdown:
      '## Type Annotations\n\nBasic types:\n\n```typescript\nlet name: string = "John";\nlet age: number = 30;\nlet active: boolean = true;\n```\n\n*TypeScript compiles to plain JavaScript.*',
  },
  {
    id: 3,
    title: 'MUI Data Grid',
    notes:
      'The MUI X Data Grid is a powerful component for displaying and editing tabular data. It supports sorting, filtering, pagination, and many other features out of the box.',
    markdown:
      '### Features\n\n1. Sorting & Filtering\n2. Pagination\n3. Column resizing\n4. Row selection\n\n> The Data Grid is highly customizable!',
  },
];

const CharacterCountWrapper = styled('div')({
  display: 'flex',
  flexDirection: 'column',
  height: '100%',
});

const CharacterCountText = styled('span')(({ theme }) => ({
  marginTop: 'auto',
  paddingTop: theme.spacing(1),
  fontSize: '0.75rem',
  color: theme.palette.text.secondary,
}));

const MarkdownPreview = styled('div')(({ theme }) => ({
  fontFamily: theme.typography.fontFamily,
  '& h1, & h2, & h3': {
    marginTop: 0,
    marginBottom: theme.spacing(1),
    fontSize: '1rem',
    fontWeight: 600,
  },
  '& p': {
    marginTop: 0,
    marginBottom: theme.spacing(1),
  },
  '& ul, & ol': {
    marginTop: 0,
    marginBottom: theme.spacing(1),
    paddingLeft: theme.spacing(2),
  },
  '& code': {
    backgroundColor: theme.palette.action.hover,
    padding: theme.spacing(0.25, 0.5),
    borderRadius: theme.shape.borderRadius,
    fontSize: '0.85em',
  },
  '& pre': {
    backgroundColor: theme.palette.action.hover,
    padding: theme.spacing(1),
    borderRadius: theme.shape.borderRadius,
    overflow: 'auto',
    '& code': {
      backgroundColor: 'transparent',
      padding: 0,
    },
  },
  '& blockquote': {
    margin: 0,
    marginBottom: theme.spacing(1),
    paddingLeft: theme.spacing(1.5),
    borderLeft: `3px solid ${theme.palette.divider}`,
    color: theme.palette.text.secondary,
  },
  '& strong': {
    fontWeight: 600,
  },
  '& em': {
    fontStyle: 'italic',
  },
}));

const columns: GridColDef[] = [
  { field: 'title', headerName: 'Title', width: 180 },
  {
    field: 'notes',
    headerName: 'Notes (with character count)',
    type: 'longText',
    width: 250,
    editable: true,
    renderCell: (params) => (
      <GridLongTextCell
        {...params}
        renderContent={(value) => (
          <CharacterCountWrapper>
            <div>{value}</div>
            <CharacterCountText>{value?.length ?? 0} characters</CharacterCountText>
          </CharacterCountWrapper>
        )}
      />
    ),
  },
  {
    field: 'markdown',
    headerName: 'Markdown (with preview)',
    type: 'longText',
    width: 250,
    editable: true,
    renderCell: (params) => (
      <GridLongTextCell
        {...params}
        renderContent={(value) => (
          <MarkdownPreview>
            <Markdown>{value ?? ''}</Markdown>
          </MarkdownPreview>
        )}
        slotProps={{
          popperContent: {
            style: {
              minWidth: 375,
              maxHeight: 'initial',
            },
          },
        }}
      />
    ),
  },
];

export default function LongTextCustomPopupContent() {
  return (
    <Box sx={{ height: 400, width: '100%' }}>
      <DataGrid rows={rows} columns={columns} />
    </Box>
  );
}

```