Advanced Tables with Calculated Columns

This example demonstrates advanced table features including automatic calculations. It shows how to create a table with calculated columns that automatically update when values change.

Features

  • Automatic Calculations: Quantity × Price = Total for each row
  • Grand Total: Automatically calculated sum of all totals
  • Real-time Updates: Calculations update immediately when you change quantity or price values
  • Split cells: Merge and split table cells
  • Cell background color: Color individual cells
  • Cell text color: Change text color in cells
  • Table row and column headers: Use headers for better organization

How It Works

The example uses the onChange event listener to detect when table content changes. When a table is updated, it automatically:

  1. Extracts quantity and price values from each data row
  2. Calculates the total (quantity × price) for each row
  3. Updates the total column with the calculated values
  4. Calculates and updates the grand total

Code Highlights

<BlockNoteView
  editor={editor}
  onChange={(editor, { getChanges }) => {
    const changes = getChanges();

    changes.forEach((change) => {
      if (change.type === "update" && change.block.type === "table") {
        const updatedRows = calculateTableTotals(change.block);
        if (updatedRows) {
          editor.updateBlock(change.block, {
            type: "table",
            content: {
              ...change.block.content,
              rows: updatedRows as any,
            } as any,
          });
        }
      }
    });
  }}
></BlockNoteView>

Relevant Docs:

import "@blocknote/core/fonts/inter.css";import { BlockNoteView } from "@blocknote/mantine";import "@blocknote/mantine/style.css";import { useCreateBlockNote } from "@blocknote/react";import type { Block, DefaultBlockSchema } from "@blocknote/core";import { useRef } from "react";export default function App() {  const applying = useRef(false);  // Creates a new editor instance.  const editor = useCreateBlockNote({    // This enables the advanced table features    tables: {      splitCells: true,      cellBackgroundColor: true,      cellTextColor: true,      headers: true,    },    initialContent: [      {        id: "7e498b3d-d42e-4ade-9be0-054b292715ea",        type: "heading",        props: {          textColor: "default",          backgroundColor: "default",          textAlignment: "left",          level: 2,        },        content: [          {            type: "text",            text: "Advanced Tables with Calculated Columns",            styles: {},          },        ],        children: [],      },      {        id: "cbf287c6-770b-413a-bff5-ad490a0b562a",        type: "table",        props: {          textColor: "default",        },        content: {          type: "tableContent",          columnWidths: [150, 120, 120, 120],          headerRows: 1,          rows: [            {              cells: [                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "Item",                      styles: { bold: true },                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "gray",                    textColor: "default",                    textAlignment: "center",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "Quantity",                      styles: { bold: true },                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "gray",                    textColor: "default",                    textAlignment: "center",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "Price ($)",                      styles: { bold: true },                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "gray",                    textColor: "default",                    textAlignment: "center",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "Total ($)",                      styles: { bold: true },                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "blue",                    textColor: "white",                    textAlignment: "center",                  },                },              ],            },            {              cells: [                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "Laptop",                      styles: {},                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "default",                    textColor: "default",                    textAlignment: "left",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "2",                      styles: {},                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "default",                    textColor: "default",                    textAlignment: "center",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "1200",                      styles: {},                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "default",                    textColor: "default",                    textAlignment: "center",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "2400",                      styles: { bold: true },                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "green",                    textColor: "white",                    textAlignment: "center",                  },                },              ],            },            {              cells: [                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "Mouse",                      styles: {},                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "default",                    textColor: "default",                    textAlignment: "left",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "5",                      styles: {},                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "default",                    textColor: "default",                    textAlignment: "center",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "25",                      styles: {},                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "default",                    textColor: "default",                    textAlignment: "center",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "125",                      styles: { bold: true },                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "green",                    textColor: "white",                    textAlignment: "center",                  },                },              ],            },            {              cells: [                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "Keyboard",                      styles: {},                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "default",                    textColor: "default",                    textAlignment: "left",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "3",                      styles: {},                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "default",                    textColor: "default",                    textAlignment: "center",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "80",                      styles: {},                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "default",                    textColor: "default",                    textAlignment: "center",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "240",                      styles: { bold: true },                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "green",                    textColor: "white",                    textAlignment: "center",                  },                },              ],            },            {              cells: [                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "Grand Total",                      styles: { bold: true },                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "yellow",                    textColor: "default",                    textAlignment: "center",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "",                      styles: {},                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "yellow",                    textColor: "default",                    textAlignment: "center",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "",                      styles: {},                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "yellow",                    textColor: "default",                    textAlignment: "center",                  },                },                {                  type: "tableCell",                  content: [                    {                      type: "text",                      text: "2765",                      styles: { bold: true },                    },                  ],                  props: {                    colspan: 1,                    rowspan: 1,                    backgroundColor: "red",                    textColor: "white",                    textAlignment: "center",                  },                },              ],            },          ],        },        children: [],      },      {        id: "16e76a94-74e5-42e2-b461-fc9da9f381f7",        type: "paragraph",        props: {          textColor: "default",          backgroundColor: "default",          textAlignment: "left",        },        content: [          {            type: "text",            text: "Features:",            styles: {},          },        ],        children: [          {            id: "785fc9f7-8554-47f4-a4df-8fe2f1438cac",            type: "bulletListItem",            props: {              textColor: "default",              backgroundColor: "default",              textAlignment: "left",            },            content: [              {                type: "text",                text: "Automatic calculation of totals (Quantity × Price)",                styles: {},              },            ],            children: [],          },          {            id: "1d0adf08-1b42-421a-b9ea-b3125dcc96d9",            type: "bulletListItem",            props: {              textColor: "default",              backgroundColor: "default",              textAlignment: "left",            },            content: [              {                type: "text",                text: "Grand total calculation",                styles: {},              },            ],            children: [],          },          {            id: "99991aa7-9d86-4d06-9073-b1a9c0329062",            type: "bulletListItem",            props: {              textColor: "default",              backgroundColor: "default",              textAlignment: "left",            },            content: [              {                type: "text",                text: "Cell background & foreground coloring",                styles: {},              },            ],            children: [],          },          {            id: "c7bf2a7c-8972-44f1-acd8-cf60fa734068",            type: "bulletListItem",            props: {              textColor: "default",              backgroundColor: "default",              textAlignment: "left",            },            content: [              {                type: "text",                text: "Splitting & merging cells",                styles: {},              },            ],            children: [],          },          {            id: "785fc9f7-8554-47f4-a4df-8fe2f1438cac",            type: "bulletListItem",            props: {              textColor: "default",              backgroundColor: "default",              textAlignment: "left",            },            content: [              {                type: "text",                text: "Header rows & columns",                styles: {},              },            ],            children: [],          },        ],      },      {        id: "c7bf2a7c-8972-44f1-acd8-cf60fa734068",        type: "paragraph",        props: {          textColor: "default",          backgroundColor: "default",          textAlignment: "left",        },        content: [],        children: [],      },    ],  });  // Function to calculate totals for a table  const calculateTableTotals = (tableBlock: Block<DefaultBlockSchema>) => {    if (tableBlock.type !== "table") return;    const rows = tableBlock.content.rows;    if (rows.length < 2) return; // Need at least header + 1 data row    let grandTotal = 0;    const updatedRows = rows.map((row, rowIndex: number) => {      if (rowIndex === 0) return row; // Skip header row      if (rowIndex === rows.length - 1) return row; // Skip grand total row      // Helper function to extract text from a cell      const getCellText = (cell: any): string => {        if (typeof cell === "string") return cell;        if (cell && typeof cell === "object" && "content" in cell) {          return cell.content?.[0]?.text || "0";        }        return "0";      };      const itemText = getCellText(row.cells[0]);      const quantityText = getCellText(row.cells[1]);      const priceText = getCellText(row.cells[2]);      const quantity = parseFloat(quantityText) || 0;      const price = parseFloat(priceText) || 0;      const total = quantity * price;      grandTotal += total;      // Update the total cell      const updatedCells = [...row.cells];      updatedCells[3] = {        type: "tableCell",        content: [          {            type: "text",            text: total.toString(),            styles: { bold: true },          },        ],        props: {          colspan: 1,          rowspan: 1,          backgroundColor: "green",          textColor: "white",          textAlignment: "center",        },      };      // Update item label if total is above 4k      const baseItemText = itemText.replace(" (eligible for discount)", "");      if (total >= 4000) {        updatedCells[0] = {          ...row.cells[0],          content: [            {              type: "text",              text: baseItemText + " (eligible for discount)",              styles: {},            },          ],        };      } else {        updatedCells[0] = {          ...row.cells[0],          content: [            {              type: "text",              text: baseItemText,              styles: {},            },          ],        };      }      return {        ...row,        cells: updatedCells,      };    });    // Update grand total row    const grandTotalRow = updatedRows[rows.length - 1];    if (grandTotalRow) {      const updatedGrandTotalCells = [...grandTotalRow.cells];      updatedGrandTotalCells[3] = {        type: "tableCell",        content: [          {            type: "text",            text: grandTotal.toString(),            styles: { bold: true },          },        ],        props: {          colspan: 1,          rowspan: 1,          backgroundColor: "red",          textColor: "white",          textAlignment: "center",        },      };      updatedRows[rows.length - 1] = {        ...grandTotalRow,        cells: updatedGrandTotalCells,      };    }    return updatedRows as typeof tableBlock.content.rows;  };  // Renders the editor instance using a React component.  return (    <BlockNoteView      editor={editor}      onChange={(editor, { getChanges }) => {        const changes = getChanges();        if (changes.length === 0 || applying.current) return;        // prevents a double onChange because we're updating the block here        applying.current = true;        changes.forEach((change) => {          if (change.type === "update" && change.block.type === "table") {            const updatedRows = calculateTableTotals(change.block);            if (updatedRows) {              // Use any type to bypass complex type checking for this demo              editor.updateBlock(change.block, {                type: "table",                content: {                  ...change.block.content,                  rows: updatedRows,                },              });            }          }        });        requestAnimationFrame(() => (applying.current = false));      }}    ></BlockNoteView>  );}