5.0
Implementing core features
Fill handle

About fill handling in ReactGrid

The fill handle is enabled by default in ReactGrid. You can disable it by passing the disableFillHandle prop to the ReactGrid component. If you want to implement custom logic for the fill handle behavior, you can use the onFillHandle prop.

onFillHandle?: (selectedArea: NumericalRange, fillRange: NumericalRange, cellsLookup: CellsLookup) => boolean;

Parameters

NameTypeDescription
selectedAreaNumericalRangeThe range of cells that were initially selected.
fillRangeNumericalRangeThe range of cells that the fill handle is dragged over.
cellsLookupCellsLookupA map object that provides callbacks to get and set cell values. Each key is a string combining row and column indices, and the value is an object with rowIndex, colIndex, onStringValueRequested (callback to get the cell value), and onStringValueReceived (callback to set the cell value).

Return Value

The return value of these handlers determines whether the custom logic should override the default fill handle behavior.

Return ValueTypeDescription
truebooleanPrevent the default fill handle behavior.
falsebooleanDefault behavior should proceed without being prevented. It is used in case you want to perform additional actions before allowing the default fill handle behavior.

Live example

Below is an example of logic that overrides the fill handle:


ReactGrid

Code


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

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

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

const getColumns = (): Column[] => [
  { colIndex: 0, width: 220 },
  { colIndex: 1, width: 220 },
  { colIndex: 2, width: 220 },
  { colIndex: 3, width: 220 },
];

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

const generateCells = (people: Person[], updatePerson: UpdatePerson): Cell[] => {
  const generateHeaderCells = () => {
    const titles = ["Name", "Age", "Email", "Company"];

    return titles.map((title, colIndex) => ({
      rowIndex: 0,
      colIndex,
      Template: NonEditableCell,
      props: {
        value: title,
        style: cellStyles.header,
      },
    }));
  };

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

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

  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, updatePerson);

  return (
    <div>
      <ReactGrid
        rows={rows}
        columns={columns}
        cells={cells}
        onFillHandle={handleFill}
        stickyLeftColumns={1}
      />
    </div>
  );
};

const handleFill = (
  selectedArea: NumericalRange,
  fillRange: NumericalRange,
  cellsLookup: CellsLookup
): boolean => {
  // Check if the fill handle is being dragged upwards
  const isFillingUpwards = fillRange.startRowIdx < selectedArea.startRowIdx;
  // Calculate the number of rows and columns in the selected area
  const relativeRowSize = selectedArea.endRowIdx - selectedArea.startRowIdx;
  const relativeColSize = selectedArea.endColIdx - selectedArea.startColIdx;

  // Iterate over the rows and columns in the fill range
  for (let i = fillRange.startRowIdx; i < fillRange.endRowIdx; i++) {
    for (let j = fillRange.startColIdx; j < fillRange.endColIdx; j++) {
      const currentCellCallbacks = cellsLookup.get(`${i} ${j}`);

      if (!currentCellCallbacks) continue;

      // Skip cells of type 'header'
      if (i === 0) continue;

      // Calculate the relative row and column indices within the selected area
      const relativeRowIdx = isFillingUpwards
        ? (selectedArea.endRowIdx - i - 1) % relativeRowSize
        : (i - fillRange.startRowIdx) % relativeRowSize;
      const relativeColIdx = (j - fillRange.startColIdx) % relativeColSize;

      // Get the value from the cell in the selected area that corresponds to the relative row and column indices
      const sourceCellCallbacks = cellsLookup.get(
        `${selectedArea.startRowIdx + relativeRowIdx} ${selectedArea.startColIdx + relativeColIdx}`
      );

      if (sourceCellCallbacks) {
        const newValue = sourceCellCallbacks.onStringValueRequested();
        currentCellCallbacks.onStringValueReceived(newValue);
      }
    }
  }

  // Override the default fill handle behavior
  return true;
};

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

Preview