5.0
Implementing core features
Column and row reordering

Column and row reordering

To enable row or column reordering in ReactGrid, the following props need to be passed to the component:

  1. For row reordering:
enableRowSelectionOnFirstColumn?: boolean;
onRowReorder?: (selectedRowIndexes: number[], destinationRowIdx: number) => void;
  1. For column reordering:
enableColumnSelectionOnFirstRow?: boolean;
onColumnReorder?: (selectedColIndexes: number[], destinationColIdx: number) => void;

Live example


ReactGrid

Code


const peopleData = [
  {
    id: "66d61077035753f369ddbb16",
    name: "Jordan Rodriquez",
    age: 30,
    email: "jordanrodriquez@cincyr.com",
    company: "Zaggles",
    position: 1,
  },
  {
    id: "66d61077794e7949ab167fd5",
    email: "allysonrios@satiance.com",
    name: "Allyson Rios",
    age: 30,
    company: "Zoxy",
    position: 2,
  },
  {
    id: "66d61077dd754e88981ae434",
    name: "Pickett Lucas",
    age: 25,
    email: "pickettlucas@zoxy.com",
    company: "Techade",
    position: 3,
  },
  {
    id: "66d61077115e2f8748c334d9",
    name: "Louella David",
    age: 37,
    email: "louelladavid@techade.com",
    company: "Ginkogene",
    position: 4,
  },
  {
    id: "66d61077540d53374b427e4b",
    name: "Tricia Greene",
    age: 27,
    email: "triciagreene@ginkogene.com",
    company: "Naxdis",
    position: 5,
  },
];

const cellStyles = {
  header: {
    backgroundColor: "#55bc71",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    fontWeight: "bold",
  },
};

const getRows = (people: Person[]): Row[] => [
  // header row
  {
    rowIndex: 0,
    height: 40,
    reorderable: false,
  },
  // data rows
  ...people.map((_, i) => ({
    rowIndex: i + 1,
    height: 40,
  })),
];

type ColumnDef = {
  colIndex: number;
  width: string | number;
  title: string;
  minWidth?: string | number;
  resizable?: boolean;
  reorderable?: boolean;
};

// Columns with fields 'title' will be used for column reordering
const getColumns = (): ColumnDef[] => [
  { colIndex: 0, width: 220, title: "Name" },
  { colIndex: 1, width: 220, title: "Age" },
  { colIndex: 2, width: 220, title: "Email" },
  { colIndex: 3, width: 220, title: "Company" },
];

const handleRowReorder = (
  peopleData: Person[],
  selectedRowIndexes: number[],
  destinationRowIdx: number,
  updatePerson: (id: string, key: string, newValue: number) => void
) => {
  const prevPeopleData = [...peopleData].sort((a, b) => a.position - b.position);

  // Adjust the destination index to account for the header row
  const adjustedDestinationIdx = destinationRowIdx - 1;
  const adjustedSelectedRowIdxs = selectedRowIndexes.map((rowIdx) => rowIdx - 1);

  const isReorderingUpwards = adjustedSelectedRowIdxs.some((rowIdx) => rowIdx > adjustedDestinationIdx);

  adjustedSelectedRowIdxs.forEach((rowIdx, index) => {
    if (adjustedDestinationIdx === 0) {
      prevPeopleData[rowIdx].position = prevPeopleData[adjustedDestinationIdx].position / 2 + index * 0.0001;
    } else if (adjustedDestinationIdx === peopleData.length - 1) {
      prevPeopleData[rowIdx].position = prevPeopleData[adjustedDestinationIdx].position + 1 + index * 0.0001;
    } else if (isReorderingUpwards) {
      prevPeopleData[rowIdx].position =
        (prevPeopleData[adjustedDestinationIdx].position + prevPeopleData[adjustedDestinationIdx - 1].position) / 2 +
        index * 0.0001;
    } else {
      prevPeopleData[rowIdx].position =
        (prevPeopleData[adjustedDestinationIdx].position + prevPeopleData[adjustedDestinationIdx + 1].position) / 2 +
        index * 0.0001;
    }
  });

  prevPeopleData.forEach((row) => {
    updatePerson(row.id, "position", row.position);
  });
};

const handleColumnReorder = (
  selectedColIndexes: number[],
  destinationColIdx: number,
  setColumns: React.Dispatch<React.SetStateAction<ColumnDef[]>>
) => {
  setColumns((prevColumns) => {
    // Filter out the selected columns and unselected columns
    const selectedColumns = prevColumns.filter((_, index) => selectedColIndexes.includes(index));
    const unselectedColumns = prevColumns.filter((_, index) => !selectedColIndexes.includes(index));

    // Adjust the destination index based on the direction of the reorder
    const adjustedDestinationColIdx =
      selectedColIndexes[0] > destinationColIdx ? destinationColIdx : destinationColIdx - selectedColumns.length + 1;

    // Create the new columns array with reordered columns
    const newColumns = [
      ...unselectedColumns.slice(0, adjustedDestinationColIdx),
      ...selectedColumns,
      ...unselectedColumns.slice(adjustedDestinationColIdx),
    ];

    return newColumns;
  });
};

type UpdatePerson = <T>(id: string, key: string, newValue: T) => void;

const generateCells = (people: Person[], columns: ColumnDef[], updatePerson: UpdatePerson): Cell[] => {
  const generateHeaderCells = () => {
    return columns.map((column, colIdx) => ({
      rowIndex: 0,
      colIndex: colIdx,
      Template: NonEditableCell,
      props: {
        value: column.title,
        style: cellStyles.header,
      },
    }));
  };

  const generateRowCells = (rowIndex: number, person: Person): Cell[] => {
    const { id, name, age, email, company } = person;

    const nameColIndex = columns.findIndex((col) => col.title === "Name");
    const ageColIndex = columns.findIndex((col) => col.title === "Age");
    const emailColIndex = columns.findIndex((col) => col.title === "Email");
    const companyColIndex = columns.findIndex((col) => col.title === "Company");

    return [
      {
        rowIndex,
        colIndex: nameColIndex,
        Template: TextCell,
        props: {
          text: name,
          onTextChanged: (newText: string) => updatePerson(id, "name", newText),
        },
      },
      {
        rowIndex,
        colIndex: ageColIndex,
        Template: NumberCell,
        props: {
          value: age,
          onValueChanged: (newValue: number) => updatePerson(id, "age", newValue),
        },
      },
      {
        rowIndex,
        colIndex: emailColIndex,
        Template: TextCell,
        props: {
          text: email,
          onTextChanged: (newText: string) => updatePerson(id, "email", newText),
        },
      },
      {
        rowIndex,
        colIndex: companyColIndex,
        Template: TextCell,
        props: {
          text: company,
          onTextChanged: (newText: string) => updatePerson(id, "company", newText),
        },
      },
    ];
  };

  people.sort((a, b) => a.position - b.position);
  const headerCells = generateHeaderCells();
  const rowCells = people.flatMap((person, idx) => generateRowCells(idx + 1, person));
  return [...headerCells, ...rowCells];
};

const ReactGridExample = () => {
  const [people, setPeople] = useState(peopleData);

  const updatePerson = (id, key, newValue) => {
    setPeople((prev) => {
      return prev.map((p) => (p.id === id ? { ...p, [key]: newValue } : p));
    });
  };

  const rows = getRows(people);
  const [columns, setColumns] = useState(getColumns());
  const cells = generateCells(people, columns, updatePerson);

  return (
    <div>
      <ReactGrid
        rows={rows}
        columns={columns}
        cells={cells}
        enableRowSelectionOnFirstColumn
        enableColumnSelectionOnFirstRow
        onRowReorder={(selectedRowIndexes, destinationRowIdx) =>
          handleRowReorder(people, selectedRowIndexes, destinationRowIdx, updatePerson)
        }
        onColumnReorder={(selectedColIndexes, destinationColIdx) =>
          handleColumnReorder(selectedColIndexes, destinationColIdx, setColumns)
        }
      />
    </div>
  );
};

render(<ReactGridExample />, document.getElementById("root"));

ReactGrid

Preview