---
title: "Master / Detail - Master Rows"
enterprise: true
framework: react
version: "36.1.0"
---

# Master / Detail - Master Rows

Master Rows are the rows inside the Master Grid that can be expanded to display Detail Grids.

## Static Master Rows

Once a Master Grid is configured with `masterDetail=true`, all rows in the Master Grid behave as Master Rows, in that they can be expanded to display Detail Grids.

```jsx
// by itself, all rows will be expandable
const masterDetail = true;

<AgGridReact masterDetail={masterDetail} />
```

Because Static Master Rows are used in all the basic examples of Master / Detail, another example is not given here.

## Dynamic Master Rows

Dynamic Master Rows allows specifically deciding what rows in the Master Grid can be expanded. This can be useful if, for example, a Master Row has no child records, then it may not be desirable to allow expanding the Master Row.

To specify which rows should expand, provide the grid callback `isRowMaster`. The callback will be called once for each row. Return `true` to allow expanding and `false` to disallow expanding for that row.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `isRowMaster` | `IsRowMaster` |  |  | Callback to be used with [Master Detail](https://www.ag-grid.com/react-data-grid/master-detail/) to determine if a row should be a master row. If `false` is returned no detail row will exist for this row. Module: [`MasterDetailModule`](https://www.ag-grid.com/react-data-grid/modules/). |

```jsx
// turn on master detail
const masterDetail = true;
// specify which rows to expand
const isRowMaster = dataItem => {
    return expandThisRow ? true : false;
};

<AgGridReact
    masterDetail={masterDetail}
    isRowMaster={isRowMaster}
/>
```

The following example only shows detail rows when there are corresponding child records.

#### Dynamic Master Rows

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GridApi,
  GridOptions,
  IDetailCellRendererParams,
  IsRowMaster,
  ModuleRegistry,
  RowApiModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  MasterDetailModule,
} from "ag-grid-enterprise";
import { useFetchJson } from "./useFetchJson";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  RowApiModule,
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  MasterDetailModule,
  ColumnMenuModule,
  ContextMenuModule,
];

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);

  const isRowMaster = useCallback((dataItem: any) => {
    return dataItem ? dataItem.callRecords.length > 0 : false;
  }, []);
  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    // group cell renderer needed for expand / collapse icons
    { field: "name", cellRenderer: "agGroupCellRenderer" },
    { field: "account" },
    { field: "calls" },
    { field: "minutes", valueFormatter: "x.toLocaleString() + 'm'" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
    };
  }, []);
  const detailCellRendererParams = useMemo<any>(() => {
    return {
      detailGridOptions: {
        columnDefs: [
          { field: "callId" },
          { field: "direction" },
          { field: "number", minWidth: 150 },
          { field: "duration", valueFormatter: "x.toLocaleString() + 's'" },
          { field: "switchCode", minWidth: 150 },
        ],
        defaultColDef: {
          flex: 1,
        },
      },
      getDetailRowData: function (params) {
        params.successCallback(params.data.callRecords);
      },
    } as IDetailCellRendererParams<IAccount, ICallRecord>;
  }, []);

  const { data, loading } = useFetchJson<any>(
    "https://www.ag-grid.com/example-assets/master-detail-dynamic-data.json",
  );

  const onFirstDataRendered = useCallback((params: FirstDataRenderedEvent) => {
    // arbitrarily expand a row for presentational purposes
    setTimeout(() => {
      params.api.getDisplayedRowAtIndex(1)!.setExpanded(true);
    }, 0);
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={gridStyle}>
          <AgGridReact
            rowData={data}
            loading={loading}
            masterDetail={true}
            isRowMaster={isRowMaster}
            columnDefs={columnDefs}
            defaultColDef={defaultColDef}
            detailCellRendererParams={detailCellRendererParams}
            onFirstDataRendered={onFirstDataRendered}
          />
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Dynamic Master Rows](https://www.ag-grid.com/examples/master-detail-master-rows/dynamic/reactFunctionalTs)

## Changing Dynamic Master Rows

The callback `isRowMaster` is re-called after data changes in the row as a result of a [Transaction Update](https://www.ag-grid.com/react-data-grid/data-update-transactions/). This gives the opportunity to change whether the row is expandable or not.

```js
// to get isRowMaster called again, update the row using a Transaction Update
const transaction = { update: [ updatedRecord1, updatedRecord2 ] };
gridApi.applyTransaction(transaction);
```

In the example below, only Master Rows that have data to show are expandable. Note the following:

- Row 'Nora Thomas' has no detail records, thus is not expandable.
- Row 'Mila Smith' has detail records, thus is expandable.
- Clicking 'Clear Mila Calls' removes detail records from Mila Smith which results in the Mila Smith row no longer being a Master Row.
- Clicking 'Set Mila Calls' sets detail records from Mila Smith which results in the Mila Smith becoming a Master Row.

#### Dynamically Changing Master Rows

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  IDetailCellRendererParams,
  IsRowMaster,
  ModuleRegistry,
  RowApiModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  MasterDetailModule,
} from "ag-grid-enterprise";
import { IAccount } from "./interfaces";
import { useFetchJson } from "./useFetchJson";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelApiModule,
  RowApiModule,
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  MasterDetailModule,
  ColumnMenuModule,
  ContextMenuModule,
];

const GridExample = () => {
  const gridRef = useRef<AgGridReact<IAccount>>(null);
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);

  const isRowMaster = useCallback((dataItem: any) => {
    return dataItem ? dataItem.callRecords.length > 0 : false;
  }, []);
  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    // group cell renderer needed for expand / collapse icons
    { field: "name", cellRenderer: "agGroupCellRenderer" },
    { field: "account" },
    { field: "calls" },
    { field: "minutes", valueFormatter: "x.toLocaleString() + 'm'" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
    };
  }, []);
  const getRowId = useCallback(
    (params: GetRowIdParams) => String(params.data.account),
    [],
  );
  const detailCellRendererParams = useMemo(() => {
    return {
      detailGridOptions: {
        columnDefs: [
          { field: "callId" },
          { field: "direction" },
          { field: "number", minWidth: 150 },
          { field: "duration", valueFormatter: "x.toLocaleString() + 's'" },
          { field: "switchCode", minWidth: 150 },
        ],
        defaultColDef: {
          flex: 1,
        },
      },
      getDetailRowData: (params) => {
        params.successCallback(params.data.callRecords);
      },
    } as IDetailCellRendererParams<IAccount, ICallRecord>;
  }, []);

  const { data, loading } = useFetchJson<IAccount>(
    "https://www.ag-grid.com/example-assets/master-detail-dynamic-data.json",
  );

  const onFirstDataRendered = useCallback((params: FirstDataRenderedEvent) => {
    // arbitrarily expand a row for presentational purposes
    setTimeout(() => {
      params.api.getDisplayedRowAtIndex(1)!.setExpanded(true);
    }, 0);
  }, []);

  const onBtClearMilaCalls = useCallback(() => {
    const milaSmithRowNode = gridRef.current!.api.getRowNode("177001")!;
    const milaSmithData = milaSmithRowNode.data!;
    milaSmithData.callRecords = [];
    milaSmithData.calls = milaSmithData.callRecords.length;
    gridRef.current!.api.applyTransaction({ update: [milaSmithData] });
  }, []);

  const onBtSetMilaCalls = useCallback(() => {
    const milaSmithRowNode = gridRef.current!.api.getRowNode("177001")!;
    const milaSmithData = milaSmithRowNode.data!;
    milaSmithData.callRecords = [
      {
        name: "susan",
        callId: 579,
        duration: 23,
        switchCode: "SW5",
        direction: "Out",
        number: "(02) 47485405",
      },
      {
        name: "susan",
        callId: 580,
        duration: 52,
        switchCode: "SW3",
        direction: "In",
        number: "(02) 32367069",
      },
    ];
    milaSmithData.calls = milaSmithData.callRecords.length;
    gridRef.current!.api.applyTransaction({ update: [milaSmithData] });
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div
          style={{ display: "flex", flexDirection: "column", height: "100%" }}
        >
          <div style={{ paddingBottom: "4px" }}>
            <button onClick={onBtClearMilaCalls}>Clear Mila Calls</button>
            <button onClick={onBtSetMilaCalls}>Set Mila Calls</button>
          </div>

          <div style={gridStyle}>
            <AgGridReact<IAccount>
              ref={gridRef}
              rowData={data}
              loading={loading}
              masterDetail={true}
              isRowMaster={isRowMaster}
              columnDefs={columnDefs}
              defaultColDef={defaultColDef}
              getRowId={getRowId}
              detailCellRendererParams={detailCellRendererParams}
              onFirstDataRendered={onFirstDataRendered}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Dynamically Changing Master Rows](https://www.ag-grid.com/examples/master-detail-master-rows/changing-dynamic-1/reactFunctionalTs)

The example below extends the previous example. It demonstrates a common scenario of the Master Row controlling the Detail Rows. Note the following:

- Each Master Row has buttons to add or remove one detail row.
- Clicking 'Add' will:
  - Add one detail row.
  - Ensure the Master Row is expandable.
  - Ensure the Master Row is expanded (i.e. the Detail Grid is visible).
- Clicking 'Remove' will:
  - Remove one detail row.
  - If no detail rows exist, ensure Master Row is not expandable

#### Dynamically Changing Master Rows

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import "./style.css";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  IDetailCellRendererParams,
  IsRowMaster,
  ModuleRegistry,
  RowApiModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  MasterDetailModule,
} from "ag-grid-enterprise";
import CallsCellRenderer from "./callsCellRenderer.tsx";
import { useFetchJson } from "./useFetchJson";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  RowApiModule,
  ClientSideRowModelModule,
  ClientSideRowModelApiModule,
  ColumnsToolPanelModule,
  MasterDetailModule,
  ColumnMenuModule,
  ContextMenuModule,
];

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);

  const isRowMaster = useCallback((dataItem: any) => {
    return dataItem ? dataItem.callRecords.length > 0 : false;
  }, []);
  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    // group cell renderer needed for expand / collapse icons
    { field: "name", cellRenderer: "agGroupCellRenderer" },
    { field: "account" },
    { field: "calls", cellRenderer: CallsCellRenderer },
    { field: "minutes", valueFormatter: "x.toLocaleString() + 'm'" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
    };
  }, []);
  const getRowId = useCallback(
    (params: GetRowIdParams) => String(params.data.account),
    [],
  );
  const detailCellRendererParams = useMemo<any>(() => {
    return {
      detailGridOptions: {
        columnDefs: [
          { field: "callId" },
          { field: "direction" },
          { field: "number", minWidth: 150 },
          { field: "duration", valueFormatter: "x.toLocaleString() + 's'" },
          { field: "switchCode", minWidth: 150 },
        ],
        defaultColDef: {
          flex: 1,
        },
      },
      getDetailRowData: (params) => {
        params.successCallback(params.data.callRecords);
      },
    } as IDetailCellRendererParams<IAccount, ICallRecord>;
  }, []);

  const { data, loading } = useFetchJson<any>(
    "https://www.ag-grid.com/example-assets/master-detail-dynamic-data.json",
  );

  const onFirstDataRendered = useCallback((params: FirstDataRenderedEvent) => {
    // arbitrarily expand a row for presentational purposes
    setTimeout(() => {
      params.api.getDisplayedRowAtIndex(1)!.setExpanded(true);
    }, 0);
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={gridStyle}>
          <AgGridReact
            rowData={data}
            loading={loading}
            masterDetail={true}
            isRowMaster={isRowMaster}
            columnDefs={columnDefs}
            defaultColDef={defaultColDef}
            getRowId={getRowId}
            detailCellRendererParams={detailCellRendererParams}
            onFirstDataRendered={onFirstDataRendered}
          />
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Dynamically Changing Master Rows](https://www.ag-grid.com/examples/master-detail-master-rows/changing-dynamic-2/reactFunctionalTs)

## Opening Master Rows by Default

Master Rows can be expanded by default using either `masterDefaultExpanded` or `isMasterOpenByDefault`.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `masterDefaultExpanded` | `number` |  |  | Master Detail: set to the number of levels of master rows to expand by default, e.g. `0` for none, `1` for first level only, etc. Set to `-1` to expand everything. If not set, falls back to `groupDefaultExpanded`. Module: [`MasterDetailModule`](https://www.ag-grid.com/react-data-grid/modules/). |
| `isMasterOpenByDefault` | `IsMasterOpenByDefault` |  |  | (Client-side Row Model only) Master Detail: allows master rows to be open by default. Module: [`MasterDetailModule`](https://www.ag-grid.com/react-data-grid/modules/). |

Set `masterDefaultExpanded` to the number of levels of Master Rows to expand by default, or `-1` to expand all Master Rows. If not set, it falls back to `groupDefaultExpanded`.

```js
const gridOptions = {
    // expand all master rows by default
    masterDefaultExpanded: -1,
};
```

For finer control, provide the `isMasterOpenByDefault` callback. It is called once for each Master Row; return `true` to expand that row by default.

```js
const gridOptions = {
    // expand specific master rows by default
    isMasterOpenByDefault: (params) => {
        return params.data.shouldExpand;
    },
};
```

> **Note**
>
> `isMasterOpenByDefault` applies to Master Rows, whereas `isGroupOpenByDefault` applies to group rows. When combining Master Detail with row grouping, each callback controls only its own row type.
