Formulas

Per-cell formulas with automatic dependency tracking, cycle detection, and live recalculation.

Any cell can hold a formula instead of a plain value — a string starting with = that references other cells and recomputes automatically when they change, backed by a dependency graph (not a full recalculation on every edit).

Writing a formula

A formula is just a cell value that starts with =. Typing one directly into an editable cell works exactly like a spreadsheet — no separate "formula mode" to opt into:

api.transaction({ cells: [{ rowId: 'row-1', colField: 'total', value: '=[row-1:price] * [row-1:quantity]' }] });

Cell references use [rowId:colField] — the grid's own row id and column field, not spreadsheet A1 notation. Reference any cell in the grid, including ones in other columns or other rows:

api.transaction({ cells: [{ rowId: 'row-3', colField: 'variance', value: '=[row-3:actual] - [row-3:budget]' }] });

Functions

SUM, AVERAGE, MIN, and MAX take a comma-separated argument list — each argument can be a cell reference, a literal number, or a nested expression:

api.transaction({ cells: [{ rowId: 'row-1', colField: 'q1Total', value: '=SUM([row-1:jan], [row-1:feb], [row-1:mar])' }] });
api.transaction({ cells: [{ rowId: 'row-1', colField: 'avgDeal', value: '=AVERAGE([row-1:deal1], [row-1:deal2], [row-1:deal3])' }] });

There's no range syntax (no SUM(A1:A10)) — list the cells explicitly, or compute the aggregate on your own data and write the result with transaction if the set of contributing cells is large or dynamic.

Arithmetic supports +, -, *, /, parentheses, and unary minus, evaluated by a dedicated safe parser — never eval().

Reading values

api.cell(rowId, field).value is always the resolved, computed value (.raw is the formula) — you never need to know whether a cell holds a formula to read it:

const total = api.cell('row-1', 'total').value; // computed number, not "=[row-1:price] * [row-1:quantity]"

Use the formula-specific methods when you need the formula itself, or want to set/clear one without going through a plain value edit:

api.getFormula('row-1', 'total'); // '=[row-1:price] * [row-1:quantity]' | undefined
api.hasFormula('row-1', 'total'); // boolean
api.transaction({ cells: [{ rowId: 'row-1', colField: 'total', value: '=[row-1:price] * [row-1:quantity]' }] });
api.transaction({ cells: [{ rowId: 'row-1', colField: 'total', value: 0 }] }); // a plain value replaces the formula

Dependency tracking & recalculation

Every formula's references are parsed into a dependency graph. Editing [row-1:price] automatically invalidates and recomputes total — and anything else that depends on total — without recomputing the rest of the grid. Results are cached until a dependency actually changes.

Circular references

Setting a formula that would create a cycle (total depends on price, price depends on total) throws immediately, synchronously, from the call that introduces it — the formula is rejected, not silently applied. A cycle that could somehow only appear at evaluation time surfaces as a thrown error from that evaluation instead of an infinite loop.

Errors

An expression that fails to evaluate (a non-numeric reference used arithmetically, for example) resolves to a string like #VALUE! (reason) rather than throwing — so a bad formula shows up visibly in the cell instead of crashing the render:

api.cell('row-1', 'total').value; // '#VALUE! (Unexpected token...)' when the formula itself is malformed

Combining with columns

Formulas work with any column — format still controls how the computed result reads as text, and parse still reads what is pasted or typed (text starting with = is a formula):

const columns: ColumnDef<Person>[] = [
	{
		field: 'score',
		header: 'Total',
		format: ({ value }) => `$${Number(value).toFixed(2)}`,
	},
];

See Columns for the full column configuration surface, and Grid API for the complete GridApi reference.

Edit on GitHub

Last updated on

On this page