---
title: "Row Dragging Between Grids"
framework: react
version: "36.1.0"
---

# Row Dragging Between Grids

Row Drag Between Grids is concerned with seamless integration among different grids, allowing records to be dragged from one grid and dropped at a specific index on another grid.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `addRowDropZone` | `Function` |  |  | Adds a drop zone outside of the grid where rows can be dropped. Module: [`RowDragModule`](https://www.ag-grid.com/react-data-grid/modules/). |
| `removeRowDropZone` | `Function` |  |  | Removes an external drop zone added by `addRowDropZone`. Module: [`RowDragModule`](https://www.ag-grid.com/react-data-grid/modules/). |
| `getRowDropZoneParams` | `Function` |  |  | Returns the `RowDropZoneParams` to be used by another grid's `addRowDropZone` method. Module: [`RowDragModule`](https://www.ag-grid.com/react-data-grid/modules/). |

## Adding a Grid as Target

To allow adding a grid as DropZone, the `getRowDropZoneParams` API method should be used in the target grid and the `addRowDropZone` in the source grid.

```js
const dropZoneParams = targetGridApi.getRowDropZoneParams({
    onDragStop: function() {
        alert('Record Dropped!');
    }
});

if (dropZoneParams) {
    sourceGridApi.addRowDropZone(dropZoneParams);

    // when the DropZone above is no longer needed
    sourceGridApi.removeRowDropZone(dropZoneParams);
}
```

In the example below, note the following:

- When you drag from one grid to another, a line will appear indicating where the row will be placed.
- Rows can be dragged from one grid to the other grid. When the row is received, it is **not** removed from the first grid. This is the choice of the example. The example could equally have removed from the other grid.
- Rows can be removed from both grids by dragging the row to the 'Trash' drop zone.
- New rows can be created by clicking on the red, green and blue buttons.

#### Two Grids with Drop Position

```tsx
'use client';
import React, {
  StrictMode,
  useCallback,
  useEffect,
  useRef,
  useState,
} from "react";
import { createRoot } from "react-dom/client";

import type {
  ColDef,
  GetRowIdParams,
  GridApi,
  GridReadyEvent,
  RowDataTransaction,
  RowDropZoneParams,
} from "ag-grid-community";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  RowApiModule,
  RowDragModule,
  RowStyleModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import { AgGridProvider, AgGridReact } from "ag-grid-react";

import "./styles.css";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelApiModule,
  RowApiModule,
  TextFilterModule,
  RowDragModule,
  RowStyleModule,
  ClientSideRowModelModule,
];

const columns: ColDef[] = [
  { field: "id", rowDrag: true },
  { field: "color" },
  { field: "value1" },
  { field: "value2" },
];

const rowClassRules = {
  "red-row": 'data.color == "Red"',
  "green-row": 'data.color == "Green"',
  "blue-row": 'data.color == "Blue"',
};

const defaultColDef: ColDef = {
  flex: 1,
  minWidth: 100,
  filter: true,
};

const GridExample = () => {
  const [leftApi, setLeftApi] = useState<GridApi | null>(null);
  const [rightApi, setRightApi] = useState<GridApi | null>(null);
  const [leftRowData, setLeftRowData] = useState<any[]>([]);
  const [rightRowData, setRightRowData] = useState<any[]>([]);

  const eLeftGrid = useRef(null);
  const eRightGrid = useRef(null);
  const eBin = useRef<HTMLElement>(null);
  const eBinIcon = useRef<HTMLElement>(null);

  let rowIdSequence = 100;

  const createDataItem = useCallback(
    (color: string) => {
      const obj = {
        id: rowIdSequence++,
        color: color,
        value1: Math.floor(window.agRandom() * 100),
        value2: Math.floor(window.agRandom() * 100),
      };

      return obj;
    },
    [rowIdSequence],
  );

  useEffect(() => {
    const createRowBlock = (blocks: number) =>
      Array.apply(null, Array(blocks || 1))
        .map(() =>
          ["Red", "Green", "Blue"].map((color) => createDataItem(color)),
        )
        .reduce((prev, curr) => prev.concat(curr), []);

    setLeftRowData(createRowBlock(2));
    setRightRowData(createRowBlock(2));
  }, [createDataItem]);

  const getRowId = (params: GetRowIdParams) => String(params.data.id);

  const addRecordToGrid = (side: string, data: any) => {
    // if data missing or data has no it, do nothing
    if (!data || data.id == null) {
      return;
    }

    const api = side === "left" ? leftApi : rightApi;
    // do nothing if row is already in the grid, otherwise we would have duplicates
    const rowAlreadyInGrid = !!api!.getRowNode(data.id);
    let transaction: RowDataTransaction;

    if (rowAlreadyInGrid) {
      console.log("not adding row to avoid duplicates in the grid");
      return;
    }

    transaction = {
      add: [data],
    };

    api!.applyTransaction(transaction);
  };

  const onFactoryButtonClick = (e: any) => {
    const button = e.currentTarget,
      buttonColor = button.getAttribute("data-color"),
      side = button.getAttribute("data-side"),
      data = createDataItem(buttonColor);

    addRecordToGrid(side, data);
  };

  const binDrop = (data: any) => {
    // if data missing or data has no id, do nothing
    if (!data || data.id == null) {
      return;
    }

    const transaction = {
      remove: [data],
    };

    [leftApi, rightApi].forEach((api) => {
      const rowsInGrid = !!api!.getRowNode(data.id);

      if (rowsInGrid) {
        api!.applyTransaction(transaction);
      }
    });
  };

  const addBinZone = (api: GridApi) => {
    const dropZone: RowDropZoneParams = {
      getContainer: () => eBinIcon.current!,
      onDragEnter: () => {
        eBin.current!.style.color = "blue";
        eBinIcon.current!.style.transform = "scale(1.5)";
      },
      onDragLeave: () => {
        eBin.current!.style.color = "";
        eBinIcon.current!.style.transform = "scale(1)";
      },
      onDragStop: (params) => {
        binDrop(params.node.data);
        eBin.current!.style.color = "";
        eBinIcon.current!.style.transform = "scale(1)";
      },
    };

    api.addRowDropZone(dropZone);
  };

  const addGridDropZone = (side: string, api: GridApi) => {
    const dropApi = side === "Left" ? rightApi : leftApi;
    const dropZone = dropApi!.getRowDropZoneParams();

    api.addRowDropZone(dropZone);
  };

  useEffect(() => {
    if (rightApi && leftApi) {
      addBinZone(rightApi);
      addBinZone(leftApi);
      addGridDropZone("Right", rightApi);
      addGridDropZone("Left", leftApi);
    }
  });

  const onGridReady = (side: string, params: GridReadyEvent) => {
    if (side === "Left") {
      setLeftApi(params.api);
    } else {
      setRightApi(params.api);
    }
  };

  const getAddRecordButton = (side: string, color: string) => (
    <button
      key={`btn_${side}_${color}`}
      className={`factory factory-${color.toLowerCase()}`}
      data-color={color}
      data-side={side.toLowerCase()}
      onClick={onFactoryButtonClick}
    >
      <i className="far fa-plus-square"></i>
      {`Add ${color}`}
    </button>
  );

  const getInnerGridCol = (side: string) => (
    <div className="inner-col">
      <div className="toolbar">
        {["Red", "Green", "Blue"].map((color) =>
          getAddRecordButton(side, color),
        )}
      </div>
      <div
        style={{ height: "100%" }}
        className="inner-col"
        ref={side === "Left" ? eLeftGrid : eRightGrid}
      >
        <AgGridReact
          defaultColDef={defaultColDef}
          getRowId={getRowId}
          rowClassRules={rowClassRules}
          rowDragManaged={true}
          suppressMoveWhenRowDragging={true}
          rowData={side === "Left" ? leftRowData : rightRowData}
          columnDefs={[...columns]}
          onGridReady={(params: GridReadyEvent) => onGridReady(side, params)}
        />
      </div>
    </div>
  );

  return (
    <AgGridProvider modules={modules}>
      <div className="example-wrapper">
        {getInnerGridCol("Left")}
        <div className="inner-col vertical-toolbar">
          <span className="bin" ref={eBin}>
            <i className="far fa-trash-alt fa-3x" ref={eBinIcon}></i>
          </span>
        </div>
        {getInnerGridCol("Right")}
      </div>
    </AgGridProvider>
  );
};

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

[Live example: Two Grids with Drop Position](https://www.ag-grid.com/examples/row-dragging-to-grid/two-grids-with-drop-position/reactFunctionalTs/)

## Dragging Multiple Records Between Grids

It is possible to drag multiple records at once from one grid to another.

In the example below, note the following:

- This example enables [Multi-Row Dragging](https://www.ag-grid.com/react-data-grid/row-dragging-managed/#multi-row-dragging) between grids using `rowDragMultiRow`.
- When `Remove Source Rows` is selected, the rows will be removed from the **Athletes** grid once they are dropped onto the **Selected Athletes** grid.
- If `Only Deselect Source Rows` is selected, all selected rows that were copied will be deselected but will not be removed.
   Note: If some rows are selected and a row that isn't selected is copied, the selected rows will remain selected.
- If `None` is selected, the rows will be copied from one grid to another and the source grid will stay as is.

#### Multiple Records with Drop Position

```tsx
'use client';
import React, { StrictMode, useCallback, useEffect, useState } from "react";
import { createRoot } from "react-dom/client";

import type {
  ColDef,
  GetRowIdParams,
  GridApi,
  GridReadyEvent,
  RowDragEndEvent,
  RowSelectionOptions,
} from "ag-grid-community";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  RowDragModule,
  RowSelectionModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import type { CustomCellRendererProps } from "ag-grid-react";
import { AgGridProvider, AgGridReact } from "ag-grid-react";

import "./styles.css";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelApiModule,
  TextFilterModule,
  RowDragModule,
  RowSelectionModule,
  ClientSideRowModelModule,
];

const SportRenderer = (props: CustomCellRendererProps) => {
  return (
    <i
      className="far fa-trash-alt"
      style={{ cursor: "pointer" }}
      onClick={() => props.api.applyTransaction({ remove: [props.node.data] })}
    ></i>
  );
};

const leftColumns: ColDef[] = [
  {
    rowDrag: true,
    maxWidth: 50,
    suppressHeaderMenuButton: true,
    suppressHeaderFilterButton: true,
    rowDragText: (params, dragItemCount) => {
      if (dragItemCount > 1) {
        return dragItemCount + " athletes";
      }
      return params.rowNode!.data.athlete;
    },
  },
  { field: "athlete" },
  { field: "sport" },
];

const rightColumns: ColDef[] = [
  {
    rowDrag: true,
    maxWidth: 50,
    suppressHeaderMenuButton: true,
    suppressHeaderFilterButton: true,
    rowDragText: (params, dragItemCount) => {
      if (dragItemCount > 1) {
        return dragItemCount + " athletes";
      }
      return params.rowNode!.data.athlete;
    },
  },
  { field: "athlete" },
  { field: "sport" },
  {
    suppressHeaderMenuButton: true,
    suppressHeaderFilterButton: true,
    maxWidth: 50,
    cellRenderer: SportRenderer,
  },
];

const defaultColDef: ColDef = {
  flex: 1,
  minWidth: 100,
  filter: true,
};

const rowSelection: RowSelectionOptions = {
  mode: "multiRow",
};

const GridExample = () => {
  const [leftApi, setLeftApi] = useState<GridApi | null>(null);
  const [rightApi, setRightApi] = useState<GridApi | null>(null);
  const [rawData, setRawData] = useState<any[]>([]);
  const [leftRowData, setLeftRowData] = useState<any[] | null>(null);
  const [rightRowData, setRightRowData] = useState<any[]>([]);
  const [radioChecked, setRadioChecked] = useState(0);

  useEffect(() => {
    if (!rawData.length) {
      fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
        .then((resp) => resp.json())
        .then((data) => {
          const athletes: any[] = [];
          let i = 0;

          while (athletes.length < 20 && i < data.length) {
            var pos = i++;
            if (athletes.some((rec) => rec.athlete === data[pos].athlete)) {
              continue;
            }
            athletes.push(data[pos]);
          }
          setRawData(athletes);
        });
    }
  }, [rawData]);

  const loadGrids = useCallback(() => {
    setLeftRowData([...rawData]);
    setRightRowData([]);
    leftApi?.deselectAll();
  }, [leftApi, rawData]);

  useEffect(() => {
    if (rawData.length) {
      loadGrids();
    }
  }, [rawData, loadGrids]);

  const reset = () => {
    setRadioChecked(0);
    loadGrids();
  };

  const onRadioChange = (e: any) => {
    setRadioChecked(parseInt(e.target.value, 10));
  };

  const getRowId = (params: GetRowIdParams) => params.data.athlete;

  const onDragStop = useCallback(
    (params: RowDragEndEvent) => {
      const nodes = params.nodes;

      if (radioChecked === 0) {
        leftApi!.applyTransaction({
          remove: nodes.map(function (node) {
            return node.data;
          }),
        });
      } else if (radioChecked === 1) {
        leftApi!.setNodesSelected({ nodes, newValue: false });
      }
    },
    [leftApi, radioChecked],
  );

  useEffect(() => {
    if (!leftApi || !rightApi) {
      return;
    }
    const dropZoneParams = rightApi.getRowDropZoneParams({ onDragStop });

    leftApi.removeRowDropZone(dropZoneParams);
    leftApi.addRowDropZone(dropZoneParams);
  }, [leftApi, rightApi, onDragStop]);

  const onGridReady = (params: GridReadyEvent, side: number) => {
    if (side === 0) {
      setLeftApi(params.api);
    }

    if (side === 1) {
      setRightApi(params.api);
    }
  };

  const getTopToolBar = () => (
    <div className="example-toolbar panel panel-default">
      <div className="panel-body">
        <div onChange={onRadioChange}>
          <input
            type="radio"
            id="move"
            name="radio"
            value="0"
            checked={radioChecked === 0}
          />{" "}
          <label htmlFor="move">Remove Source Rows</label>
          <input
            type="radio"
            id="deselect"
            name="radio"
            value="1"
            checked={radioChecked === 1}
          />{" "}
          <label htmlFor="deselect">Only Deselect Source Rows</label>
          <input
            type="radio"
            id="none"
            name="radio"
            value="2"
            checked={radioChecked === 2}
          />{" "}
          <label htmlFor="none">None</label>
        </div>
        <span className="input-group-button">
          <button
            type="button"
            className="btn btn-default reset"
            style={{ marginLeft: "5px" }}
            onClick={reset}
          >
            <i className="fas fa-redo" style={{ marginRight: "5px" }}></i>Reset
          </button>
        </span>
      </div>
    </div>
  );

  const getGridWrapper = (id: number) => (
    <div className="panel panel-primary" style={{ marginRight: "10px" }}>
      <div className="panel-heading">
        {id === 0 ? "Athletes" : "Selected Athletes"}
      </div>
      <div className="panel-body" style={{ height: "100%" }}>
        <AgGridReact
          defaultColDef={defaultColDef}
          getRowId={getRowId}
          rowDragManaged={true}
          rowSelection={id === 0 ? rowSelection : undefined}
          rowDragMultiRow={id === 0}
          suppressMoveWhenRowDragging={id === 0}
          rowData={id === 0 ? leftRowData : rightRowData}
          columnDefs={id === 0 ? leftColumns : rightColumns}
          onGridReady={(params) => onGridReady(params, id)}
        />
      </div>
    </div>
  );

  return (
    <AgGridProvider modules={modules}>
      <div className="top-container">
        {getTopToolBar()}
        <div className="grid-wrapper">
          {getGridWrapper(0)}
          {getGridWrapper(1)}
        </div>
      </div>
    </AgGridProvider>
  );
};

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

[Live example: Multiple Records with Drop Position](https://www.ag-grid.com/examples/row-dragging-to-grid/two-grids-with-multiple-records/reactFunctionalTs/)
