5.0
Create your own cell template

Custom cell templates

Creating a cell template is the best way to customize data visualization and behavior in ReactGrid. You can define your own one and then use it as other built-in cell templates. For example if you want use custom text cell you can copy default TextCell component and modify it as you want.

The key to implementing a custom cell is integrating it with the CellWrapper, which handles communication between the cell and ReactGrid.

<CellWrapper
  onStringValueRequested={() => value?.toString() || ""}
  onStringValueReceived={() => {
    // Logic to update the cell value during operations like cut, copy, paste or fill
  }}
>
  {/* Render cell content here */}
</CellWrapper>

onStringValueRequested and onStringValueReceived are required props for the CellWrapper component. These props handle retrieving and updating cell values through the cellsLookup.

type CellWrapperProps = React.HTMLAttributes<HTMLDivElement> & {
  onStringValueRequested: () => string;
  onStringValueReceived: (v: string) => void;
  children?: React.ReactNode;
};

When creating your own cell template, you can also use the useCellContext hook to get details about a specific cell.

Example

Let's create a custom DateCell template:
ReactGrid

Code


const peopleData = [
  {
    id: "66d61077035753f369ddbb16",
    name: "Jordan Rodriquez",
    birth: moment("1991-01-01"),
    email: "jordanrodriquez@cincyr.com",
    company: "Zaggles",
  },
  {
    id: "66d61077794e7949ab167fd5",
    email: "allysonrios@satiance.com",
    name: "Allyson Rios",
    birth: moment("1991-01-01"),
    company: "Zoxy",
  },
  {
    id: "66d61077dd754e88981ae434",
    name: "Pickett Lucas",
    birth: moment("1996-01-01"),
    email: "pickettlucas@zoxy.com",
    company: "Techade",
  },
  {
    id: "66d61077115e2f8748c334d9",
    name: "Louella David",
    birth: moment("1984-01-01"),
    email: "louelladavid@techade.com",
    company: "Ginkogene",
  },
  {
    id: "66d61077540d53374b427e4b",
    name: "Tricia Greene",
    birth: moment("1994-01-01"),
    email: "triciagreene@ginkogene.com",
    company: "Naxdis",
  },
];

interface DateCellProps {
    value: Moment;
    onValueChanged: (data: Moment) => void;
}

const DateCell: FC<DateCellProps> = ({ value, onValueChanged }) => {
    const ctx = useCellContext();
    const targetInputRef = useRef<HTMLInputElement>(null);
    const [isEditMode, setEditMode] = useState(false);

    let formattedDate: string | undefined;

    if (!value) {
        formattedDate = "";
    } else {
        formattedDate = value.format("DD-MM-YYYY");
    }

    return (
        <CellWrapper
            onStringValueRequested={() => value.toDate().toDateString()}
            onStringValueReceived={(v) => onValueChanged?.(moment(v))}
            style={{ padding: ".2rem", textAlign: "center", outline: "none" }}
            onDoubleClick={() => {
                ctx.isFocused && setEditMode(true);
            }}
            onKeyDown={(e) => {
                if (!isEditMode && e.key === "Enter") {
                    e.stopPropagation();
                    setEditMode(true);
                }
            }}
        >
            {isEditMode ? (
                <input
                    className="rg-input"
                    type="date"
                    defaultValue={value.format("YYYY-MM-DD")}
                    onBlur={(e) => {
                        const changedDate = e.currentTarget.value;
                        changedDate && onValueChanged(moment(e.currentTarget.value));
                        setEditMode(false);
                    }}
                    onPointerDown={(e) => e.stopPropagation()}
                    onKeyDown={(e) => {
                        if (e.key === "Escape") {
                            setEditMode(false);
                        } else if (e.key === "Enter") {
                            onValueChanged(moment(e.currentTarget.value));
                            setEditMode(false);
                        }
                    }}
                    autoFocus
                    ref={targetInputRef}
                />
            ) : (
                formattedDate
            )}
        </CellWrapper>
    );
};

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", "Birth", "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, birth, email, company } = person;

    return [
      {
        rowIndex,
        colIndex: 0,
        Template: TextCell,
        props: {
          text: name,
          onTextChanged: (newText: string) => updatePerson(id, "name", newText),
        },
      },
      {
        rowIndex,
        colIndex: 1,
        Template: DateCell,
        props: {
          value: birth,
          onValueChanged: (newValue: number) => updatePerson(id, "birth", 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 = getColumns();
  const cells = generateCells(people, updatePerson);

  return (
    <div>
      <ReactGrid
        rows={rows}
        columns={columns}
        cells={cells}
      />
    </div>
  );
};

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

ReactGrid

Preview