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: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"));
Preview