React Data Grid Row Reordering

Row reordering

Let users drag rows into a new order with a per-row grip — the React Data Grid hands you the reordered array and never mutates your data.

Some lists have an order that only a person can decide: a task backlog, a playlist, the steps of a workflow. rowOrder gives each row a grip, and the grid tells you where the row was dropped — you own the data, so you own the new order.

Row reordering

rowOrder adds a narrow handle column before the data columns. Only the grip starts a drag: a row is full of interactive content — checkboxes, editors, custom cells — that a whole-row handle would swallow.

import { CDataGrid } from '@coreui/react-data-grid'
import { useState } from 'react'

const initialItems = [
  {
 id: 1, task: 'Draft the release notes', owner: 'Alice', status: 'In progress'
},
  {
 id: 2, task: 'Review the API surface', owner: 'Bob', status: 'Blocked'
},
  {
 id: 3, task: 'Ship the beta', owner: 'Carol', status: 'Todo'
},
  {
 id: 4, task: 'Update the changelog', owner: 'Dave', status: 'Todo'
}
]

export const DataGridRowOrderExample = () => {
  const [items, setItems] = useState(initialItems)

  return (
    <CDataGrid
      columns={[
        { key: 'task', label: 'Task' },
        { key: 'owner', label: 'Owner' },
        { key: 'status', label: 'Status' }
      ]}
      items={items}
      itemKey={item => String(item.id)}
      rowOrder
      sorting={false}
      virtualization={false}
      onRowOrderChange={event => setItems(event.items as typeof initialItems)}
    />
  )
}
import { CDataGrid } from '@coreui/react-data-grid'
import { useState } from 'react'

const initialItems = [
  {
 id: 1, task: 'Draft the release notes', owner: 'Alice', status: 'In progress'
},
  {
 id: 2, task: 'Review the API surface', owner: 'Bob', status: 'Blocked'
},
  {
 id: 3, task: 'Ship the beta', owner: 'Carol', status: 'Todo'
},
  {
 id: 4, task: 'Update the changelog', owner: 'Dave', status: 'Todo'
}
]

export const DataGridRowOrderExample = () => {
  const [items, setItems] = useState(initialItems)

  return (
    <CDataGrid
      columns={[
        { key: 'task', label: 'Task' },
        { key: 'owner', label: 'Owner' },
        { key: 'status', label: 'Status' }
      ]}
      items={items}
      itemKey={item => String(item.id)}
      rowOrder
      sorting={false}
      virtualization={false}
      onRowOrderChange={event => setItems(event.items as typeof initialItems)}
    />
  )
}

The contract

The grid never mutates items. Dropping a row fires onRowOrderChange with everything needed to apply the move — the same contract inline editing uses:

PayloadDescription
itemsThe full array in its new order, ready to assign or send to your API.
itemThe row that moved.
oldIndexIts index in items before the move.
newIndexIts index after it.

Until you apply it, nothing changes on screen. That is deliberate: the order is usually persisted, and a row that snaps back when the request fails is better than one that lies.

The indexes are positions in items, not in the rendered window — in pagination mode the page offset is already added, so dragging the first row of page 2 reports oldIndex: 10, not 0.

The drag interaction

Row dragging is the same pointer-based controller as column dragging, turned on its side: a ghost with the row’s first cell, a live preview where the rows it passes slide aside and it slides into the gap, edge auto-scroll for long lists, and Escape to cancel. It works the same with a mouse, a pen and a finger, and honours prefers-reduced-motion.

Options

OptionTypeDefaultDescription
rowOrderbooleanfalseAdds a drag handle per row for reordering.
rowHandleIconReactNodegripCustom handle icon replacing the default one.

The handle’s accessible name comes from labels.reorderRow — see localization.