Skip to content

Migration from 1.x to 2.x

@gridsheet/react-right-menu is deprecated and will no longer be maintained.

  • Do not use this package for new projects.
  • Please consider using the core package (@gridsheet/react-core) or other alternatives for right-click menu functionality.

Complete guide for upgrading your GridSheet implementation from version 1.x to 2.x.

Version 2.x introduces significant improvements and breaking changes. This guide will help you migrate your existing code safely.

1. TableRef and StoreRef Replaced with Connector

Section titled “1. TableRef and StoreRef Replaced with Connector”

Before (v1.x):

import { useTableRef, useStoreRef } from '@gridsheet/react-core';
const tableRef = useTableRef();
const storeRef = useStoreRef();
<GridSheet tableRef={tableRef} storeRef={storeRef} initialCells={initialCells} />
// Access table
const { table, apply } = tableRef.current;
// Access store
const { store, apply } = storeRef.current;

After (v2.x):

import { useConnector } from '@gridsheet/react-core';
const connector = useConnector();
<GridSheet connector={connector} initialCells={initialCells} />
// Access table manager
const { tableManager } = connector.current;
const { table, apply } = tableManager;
// Access store manager
const { storeManager } = connector.current;
const { store, apply } = storeManager;

The separate useTableRef() and useStoreRef() hooks have been unified into a single useConnector() hook that provides access to both table and store managers.

Before (1.x):

import { useTableRef, useStoreRef } from '@gridsheet/react-core';
const tableRef = useTableRef();
const storeRef = useStoreRef();

After (2.x):

import { useConnector } from '@gridsheet/react-core';
const connector = useConnector();
// Access table manager
const { tableManager } = connector.current;
const { table, apply } = tableManager;
// Access store manager
const { storeManager } = connector.current;
const { store, apply } = storeManager;
  • useTableRef()useConnector()
  • useStoreRef()useConnector() (same hook for both)
  • createTableRef()createConnector()
  • createStoreRef()createConnector() (same function for both)

Renderer methods now use typed props objects instead of individual parameters.

Before (1.x):

const ScoreColorRendererMixin: RendererMixinType = {
number(value: number) {
if (value < 60) {
return { style: { backgroundColor: "#ffcccc" } };
} else if (value < 80) {
return { style: { backgroundColor: "#ffffcc" } };
} else {
return { style: { backgroundColor: "#ccffcc" } };
}
}
}

After (2.x):

const ScoreColorRendererMixin: RendererMixinType = {
number({ value }: RenderProps<number>) {
if (value < 60) {
return { style: { backgroundColor: "#ffcccc" } };
} else if (value < 80) {
return { style: { backgroundColor: "#ffffcc" } };
} else {
return { style: { backgroundColor: "#ccffcc" } };
}
}
}

Several utility functions have been renamed for consistency.

Before (1.x):

import { constructInitialCells, constructInitialCellsOrigin } from "@gridsheet/react-core";

After (2.x):

import { buildInitialCells, buildInitialCellsFromOrigin } from "@gridsheet/react-core";

headerHeight and headerWidth options have been moved from global Options to Table-specific settings.

Before (1.x):

<GridSheet
options={{
headerHeight: 100,
headerWidth: 100,
}}
/>

After (2.x):

<GridSheet
initialCells={buildInitialCells({
cells: {
0: {
width: 100, // Set header width
height: 100, // Set header height
},
}
})}
/>

And if you need to set header height and width for the table, you can do it like this:

table.setHeaderHeight(100);
table.setHeaderWidth(100);

Several table methods have been renamed for better clarity and consistency.

Before (1.x):

// Data access methods
table.getMatrixFlatten(args?: GetFlattenPropsWithArea): any[][];
table.getObjectFlatten(args?: GetFlattenProps): CellsByAddressType;
table.getRowsFlatten(args?: GetFlattenProps): CellsByAddressType[];
table.getColsFlatten(args?: GetFlattenProps): CellsByAddressType[];
// Row and column operations
table.addRowsAndUpdate(args: { y: number; numRows: number; baseY: number; diff: CellsByAddressType; partial?: boolean; updateChangedAt?: boolean; reflection?: StoreReflectionType }): UserTable;
table.addRows(args: { y: number; numRows: number; baseY: number; reflection?: StoreReflectionType }): UserTable;
table.deleteRows(args: { y: number; numRows: number; reflection?: StoreReflectionType }): UserTable;
table.addColsAndUpdate(args: { x: number; numCols: number; baseX: number; diff: CellsByAddressType; partial?: boolean; updateChangedAt?: boolean; reflection?: StoreReflectionType }): UserTable;
table.addCols(args: { x: number; numCols: number; baseX: number; reflection?: StoreReflectionType }): UserTable;
table.deleteCols(args: { x: number; numCols: number; reflection?: StoreReflectionType }): UserTable;

After (2.x):

// Data access methods
table.getFieldMatrix(args?: GetFieldPropsWithArea): any[][];
table.getFieldObject(args?: GetFieldProps): { [address: Address]: any };
table.getFieldRows(args?: GetFieldProps): { [address: Address]: any }[];
table.getFieldCols(args?: GetFieldProps): { [address: Address]: any }[];
// Row and column operations (merged functionality)
table.insertRows(args: { y: number; numRows: number; baseY: number; diff?: CellsByAddressType; partial?: boolean; updateChangedAt?: boolean; reflection?: StorePatchType }): UserTable;
table.removeRows(args: { y: number; numRows: number; reflection?: StorePatchType }): UserTable;
table.insertCols(args: { x: number; numCols: number; baseX: number; diff?: CellsByAddressType; partial?: boolean; updateChangedAt?: boolean; reflection?: StorePatchType }): UserTable;
table.removeCols(args: { x: number; numCols: number; reflection?: StorePatchType }): UserTable;

The GetProps type has been updated to use a more explicit refEvaluation property instead of the boolean evaluates property.

Before (1.x):

type GetProps = {
// null for the system, do not use it
evaluates?: boolean | null;
raise?: boolean;
filter?: CellFilter;
};

After (2.x):

type GetProps = {
// do not use 'SYSTEM', it is reserved for internal use.
refEvaluation?: RefEvaluation;
raise?: boolean;
filter?: CellFilter;
};

Usage Examples:

// Before (1.x)
table.getCell({ y: 1, x: 1 }, { evaluates: true });
table.getCell({ y: 1, x: 1 }, { evaluates: false });
table.getCell({ y: 1, x: 1 }, { evaluates: null });
// After (2.x)
table.getCell({ y: 1, x: 1 }, { refEvaluation: 'COMPLETE' });
table.getCell({ y: 1, x: 1 }, { refEvaluation: 'RAW' });
table.getCell({ y: 1, x: 1 }, { refEvaluation: 'SYSTEM' });

RefEvaluation Values:

  • 'COMPLETE' - Evaluates formulas and returns the final value (equivalent to evaluates: true)
  • 'RAW' - Returns the raw cell value without formula evaluation (equivalent to evaluates: false)
  • 'SYSTEM' - Reserved for internal use (equivalent to evaluates: null)

6. Event Handlers Moved from Options to Hub

Section titled “6. Event Handlers Moved from Options to Hub”

Event handlers have been moved from the options prop to the hub configuration for better organization and consistency.

Before (1.x):

<GridSheet
options={{
onChange: (table, positions) => {
console.log('Data changed:', positions);
},
onSave: (table, positions) => {
console.log('Data saved:', positions);
},
onSelect: (table, positions) => {
console.log('Selection changed:', positions);
},
}}
initialCells={initialCells}
/>

After (2.x):

const hub = useHub({
onChange: ({ table, points }) => {
console.log('Data changed:', points);
},
onSave: ({ table, points }) => {
console.log('Data saved:', points);
},
onSelect: ({ table, points }) => {
console.log('Selection changed:', points);
},
onRemoveRows: ({ table, ys }) => {
console.log('Rows removed:', ys);
},
onRemoveCols: ({ table, xs }) => {
console.log('Columns removed:', xs);
},
onInsertRows: ({ table, y, numRows }) => {
console.log('Rows inserted at position', y, 'count:', numRows);
},
onInsertCols: ({ table, x, numCols }) => {
console.log('Columns inserted at position', x, 'count:', numCols);
},
onKeyUp: ({ e, points }) => {
console.log('Key pressed:', e.key, 'at position:', points);
},
onInit: ({ table }) => {
console.log('Table initialized:', table.sheetName);
},
});
<GridSheet hub={hub} initialCells={initialCells} />

New Event Handlers in 2.x:

  • onRemoveRows: Called when rows are removed from the spreadsheet
  • onRemoveCols: Called when columns are removed from the spreadsheet
  • onInsertRows: Called when rows are inserted into the spreadsheet
  • onInsertCols: Called when columns are inserted into the spreadsheet
  • onKeyUp: Called when a key is pressed in the cell editor
  • onInit: Called when the table is initialized

When multiple sheets share the same hub, event handlers receive a table parameter that contains the UserTable instance. You can use table.sheetName to identify which sheet triggered the event:

const hub = useHub({
onChange: ({ table, points }) => {
// Use table.sheetName to identify the source sheet
if (table.sheetName === 'Sales') {
console.log('Sales sheet data changed:', points);
} else if (table.sheetName === 'Inventory') {
console.log('Inventory sheet data changed:', points);
}
},
onRemoveRows: ({ table, ys }) => {
console.log(`Rows removed from ${table.sheetName}:`, ys);
},
onInsertCols: ({ table, x, numCols }) => {
console.log(`Inserted ${numCols} columns at position ${x} in ${table.sheetName}`);
},
onKeyUp: ({ e, points }) => {
console.log(`Key pressed: ${e.key} at position:`, points);
},
onInit: ({ table }) => {
console.log(`Table initialized: ${table.sheetName}`);
},
});

Important: Always use table.sheetName for conditional logic when multiple sheets share the same hub to ensure proper event handling and data management.

7. Connector Function Renaming: dispatchsync

Section titled “7. Connector Function Renaming: dispatch → sync”

The function name in the connector has been renamed from dispatch to sync to avoid confusion with Redux dispatch and better reflect its purpose.

Before (1.x):

import { useTableRef } from '@gridsheet/react-core';
function MyComponent() {
const tableRef = useTableRef();
const handleUpdate = () => {
if (tableRef.current) {
const { table, dispatch } = tableRef.current;
const updatedTable = table.update({
diff: { 'A1': { value: 'Updated' } }
});
dispatch(updatedTable);
}
};
}

After (2.x):

import { useConnector } from '@gridsheet/react-core';
function MyComponent() {
const connector = useConnector();
const handleUpdate = () => {
const { tableManager } = connector.current;
if (tableManager) {
const { table, apply } = tableManager;
table.update({
diff: { 'A1': { value: 'Updated' } }
});
apply(table);
}
};
}

Type Definition:

// Before (1.x)
type TableRef = {
table: UserTable;
dispatch: (table: UserTable) => void;
};
// After (2.x)
type Connector = {
tableManager: {
instance: UserTable;
sync: (table: UserTable) => void;
};
storeManager: {
instance: StoreType;
sync: Dispatcher;
};
};
import * as React from "react";
import {
GridSheet,
constructInitialCells,
BaseFunction,
prevention,
SheetProvider,
} from "@gridsheet/react-core";
const ScoreColorRendererMixin: RendererMixinType = {
number(value: number) {
if (value < 60) {
return { style: { backgroundColor: "#ffcccc" } };
} else if (value < 80) {
return { style: { backgroundColor: "#ffffcc" } };
} else {
return { style: { backgroundColor: "#ccffcc" } };
}
}
}
function App() {
return (
<SheetProvider>
<GridSheet
sheetName={'criteria'}
initialCells={constructInitialCells({
matrices: {
A1: [
[0, "=A1+60", "=B1+10", "=C1+10", "=D1+10", "=E1+5"],
["E🤯", "D🥺", "C😒", "B😚", "A🥰", "S😇"],
],
},
cells: {
1: {
style: { backgroundColor: '#ddd' }
},
},
})}
options={{
headerHeight: 100,
headerWidth: 100,
}}
/>
<GridSheet
sheetName={'grades'}
initialCells={constructInitialCells({
matrices: {
A1: [
["Name", "Score", "Rank", "Comment"],
[
"apple",
50,
"=HLOOKUP(B2, criteria!$A$1:$F$2, 2, true)",
"Pie",
],
["orange", 82, "=HLOOKUP(B3, criteria!$A$1:$F$2, 2, true)"],
["grape", 75, "=HLOOKUP(B4, criteria!$A$1:$F$2, 2, true)"],
["melon", 98, "=HLOOKUP(B5, criteria!$A$1:$F$2, 2, true)"],
["banana", 65, "=HLOOKUP(B6, criteria!$A$1:$F$2, 2, true)"],
],
},
cells: {
B: {
renderer: ScoreColorRendererMixin,
}
}
})}
/>
</SheetProvider>
);
}
import * as React from "react";
import {
GridSheet,
buildInitialCells,
useHub,
Renderer,
RendererMixinType,
RenderProps,
makeBorder,
} from "@gridsheet/react-core";
// Updated renderer with new method signature
const ScoreColorRendererMixin: RendererMixinType = {
number({ value }: RenderProps<number>) {
if (value < 60) {
return { style: { backgroundColor: "#ffcccc" } };
} else if (value < 80) {
return { style: { backgroundColor: "#ffffcc" } };
} else {
return { style: { backgroundColor: "#ccffcc" } };
}
}
}
function App() {
const hub = useHub({
renderers: {
scoreColor: new Renderer({ mixins: [ScoreColorRendererMixin] }),
},
});
return (
<>
<GridSheet
hub={hub}
sheetName={'criteria'}
initialCells={buildInitialCells({
matrices: {
A1: [
[0, "=A1+60", "=B1+10", "=C1+10", "=D1+10", "=E1+5"],
["E🤯", "D🥺", "C😒", "B😚", "A🥰", "S😇"],
],
},
cells: {
1: {
style: {
backgroundColor: '#ddd',
...makeBorder({ all: '1px solid #ccc' })
}
},
},
})}
/>
<GridSheet
hub={hub}
sheetName={'grades'}
initialCells={buildInitialCells({
matrices: {
A1: [
["Name", "Score", "Rank", "Comment"],
[
"apple",
50,
"=HLOOKUP(B2, criteria!$A$1:$F$2, 2, true)",
"Pie",
],
["orange", 82, "=HLOOKUP(B3, criteria!$A$1:$F$2, 2, true)"],
["grape", 75, "=HLOOKUP(B4, criteria!$A$1:$F$2, 2, true)"],
["melon", 98, "=HLOOKUP(B5, criteria!$A$1:$F$2, 2, true)"],
["banana", 65, "=HLOOKUP(B6, criteria!$A$1:$F$2, 2, true)"],
],
},
cells: {
B: {
renderer: 'scoreColor',
}
}
})}
/>
</>
);
}
// Old imports
import { constructInitialCells, SheetProvider, useTableRef, useStoreRef } from '@gridsheet/react-core';
// New imports
import { buildInitialCells, useHub, Renderer, makeBorder, useConnector } from '@gridsheet/react-core';

Replace useTableRef and useStoreRef with useConnector:

// Before (1.x)
const tableRef = useTableRef();
const storeRef = useStoreRef();
// After (2.x)
const connector = useConnector();
// Access table manager
const { tableManager } = connector.current;
const { table, apply } = tableManager;
// Access store manager
const { storeManager } = connector.current;
const { store, apply } = storeManager;

Update all renderer mixins to use the new method signature with RenderProps:

// Before
const MyRenderer = {
string(value: string) { /* ... */ }
}
// After
const MyRenderer: RendererMixinType = {
string({ value }: RenderProps<string>) { /* ... */ }
}
const hub = useHub({
renderers: {
myRenderer: new Renderer({ mixins: [MyRenderer] }),
},
// Move event handlers from options to hub
onChange: ({ table, points }) => {
console.log('Data changed:', points);
},
onSave: ({ table, points }) => {
console.log('Data saved:', points);
},
onSelect: ({ table, points }) => {
console.log('Selection changed:', points);
},
onRemoveRows: ({ table, ys }) => {
console.log('Rows removed:', ys);
},
onRemoveCols: ({ table, xs }) => {
console.log('Columns removed:', xs);
},
onInsertRows: ({ table, y, numRows }) => {
console.log('Rows inserted at position', y, 'count:', numRows);
},
onInsertCols: ({ table, x, numCols }) => {
console.log('Columns inserted at position', x, 'count:', numCols);
},
onKeyUp: ({ e, points }) => {
console.log('Key pressed:', e.key, 'at position:', points);
},
onInit: (table) => {
console.log('Table initialized:', table.sheetName);
},
});

Move all event handlers from the options prop to the hub configuration:

// Before (1.x)
<GridSheet
options={{
onChange: (table, positions) => { /* ... */ },
onSave: (table, positions) => { /* ... */ },
onSelect: (table, positions) => { /* ... */ },
}}
initialCells={initialCells}
/>
// After (2.x)
const hub = useHub({
onChange: ({ table, points }) => { /* ... */ },
onSave: ({ table, points }) => { /* ... */ },
onSelect: ({ table, points }) => { /* ... */ },
// New event handlers available in 2.x
onRemoveRows: ({ table, ys }) => { /* ... */ },
onRemoveCols: ({ table, xs }) => { /* ... */ },
onInsertRows: ({ table, y, numRows }) => { /* ... */ },
onInsertCols: ({ table, x, numCols }) => { /* ... */ },
onKeyUp: ({ e, points }) => { /* ... */ },
onInit: ({ table }) => { /* ... */ },
});
<GridSheet hub={hub} initialCells={initialCells} />

Remove headerHeight and headerWidth from options and set them on the table if needed.

  • Test all features to ensure they work as expected
  • Check TypeScript errors and fix type issues
  • Verify performance improvements
  • Test edge cases and error handling

Update any direct table method calls to use the new method names:

// Before (1.x)
const matrix = table.getMatrixFlatten({ area: { top: 0, left: 0, bottom: 2, right: 2 } });
const cells = table.getObjectFlatten({ evaluates: true });
const rows = table.getRowsFlatten();
const cols = table.getColsFlatten();
table.addRows({ y: 2, numRows: 1, baseY: 2 });
table.deleteRows({ y: 2, numRows: 1 });
table.addCols({ x: 2, numCols: 1, baseX: 2 });
table.deleteCols({ x: 2, numCols: 1 });
// After (2.x)
const matrix = table.getFieldMatrix({ area: { top: 0, left: 0, bottom: 2, right: 2 } });
const cells = table.getFieldObject({ refEvaluation: 'COMPLETE' });
const rows = table.getFieldRows();
const cols = table.getFieldCols();
table.insertRows({ y: 2, numRows: 1, baseY: 2 });
table.removeRows({ y: 2, numRows: 1 });
table.insertCols({ x: 2, numCols: 1, baseX: 2 });
table.removeCols({ x: 2, numCols: 1 });

Note: The addRowsAndUpdate and addColsAndUpdate methods have been merged into insertRows and insertCols respectively. Use the diff parameter to update cells after insertion:

// Before (1.x)
table.addRowsAndUpdate({
y: 2,
numRows: 1,
baseY: 2,
diff: { 'A2': { value: 'New Row' } }
});
// After (2.x)
table.insertRows({
y: 2,
numRows: 1,
baseY: 2,
diff: { 'A2': { value: 'New Row' } }
});

Update any usage of the GetProps type to use the new refEvaluation property:

// Before (1.x)
table.getCell({ y: 1, x: 1 }, { evaluates: true });
table.getCell({ y: 1, x: 1 }, { evaluates: false });
// After (2.x)
table.getCell({ y: 1, x: 1 }, { refEvaluation: 'COMPLETE' });
table.getCell({ y: 1, x: 1 }, { refEvaluation: 'RAW' });

Update connector usage to use sync instead of dispatch:

// Before (1.x)
if (tableRef.current) {
const { table, dispatch } = tableRef.current;
const updatedTable = table.update({ diff: { 'A1': { value: 'New' } } });
dispatch(updatedTable);
}
// After (2.x)
const { tableManager } = connector.current;
if (tableManager) {
const { table, apply } = tableManager;
table.update({ diff: { 'A1': { value: 'New' } } });
apply(table);
}