Managing state - Controlled mode

Managing SpreadSheet state externally and syncing internal state like Undo/redo is easy using produceState. ProduceState adds undo/redo support. Thats the only difference.

You are free to use React setState, if you do not require undo/redo.

Using produceState

Diffs between the current state and previous state is generated by Immer. We recommend using immer to manage state as it lets you immutability update deeply nested state easily.

import React, { useState, useRef } from 'react'
import SpreadSheet, { defaultSheets, produceState } from '@rowsncolumns/spreadsheet'
function App () {
const gridRef = useRef()
const [ sheets, setSheets ] = useState(defaultSheets)
const handleClick = () => {
setSheets(prev => {
// Undo state is managed automatically.
// This uses immer internally.
// draft is an immer Proxy
// Similar to `immer` produce, but 3rd argument is required so we can save patches
return produceState(prev, draft => {
const sheet = draft.sheets[0]
if (sheet) {
sheet.cells[1] = sheet.cells[1] ?? {}
sheet.cells[1][1] = sheet.cells[1][1] ?? {}
sheet.cells[1][1].text = 'Hello world'
}
}, gridRef);
})
}
return (
<div>
<button onClick={handleClick}>Update spreadsheet</button>
<SpreadSheet
ref={gridRef}
sheets={sheets}
onChange={setSheets}
/>
</div>
)
}

Demo

Click button to update cell. Undo will be active

fx

Sheet1

Using setState and Immer

import { produceWithPatches } from 'immer'
import React, { useState, useRef } from 'react'
import SpreadSheet, { defaultSheets } from '@rowsncolumns/spreadsheet'
function App () {
const gridRef = useRef()
const [ sheets, setSheets ] = useState(defaultSheets)
const handleClick = () => {
setSheets(prev => {
const [ newState, patches, inversePatches ] = produceWithPatches({ sheets: prev }, draft => {
const sheet = draft.sheets[0]
if (sheet) {
sheet.cells[1] = sheet.cells[1] ?? {}
sheet.cells[1][1] = sheet.cells[1][1] ?? {}
sheet.cells[1][1].text = 'Hello world'
}
});
// This updates the internal undo state
gridRef.current.addUndoPatch({ patches, inversePatches })
return newState.sheets
})
}
return (
<div>
<button onClick={handleClick}>Update spreadsheet</button>
<SpreadSheet
ref={gridRef}
sheets={sheets}
onChange={setSheets}
/>
</div>
)
}

Demo

Click button to update cell. Undo will be active

fx

Sheet1

Imperative methods

You can also update cells imperatively by accessing gridRef.current.dispatch

const App = () => {
const gridRef = useRef()
const [ sheets, setSheets ] = useState(defaultSheets)
const handleClick = () => {
gridRef.current.dispatch({
type: 'UPDATE_CELLS',
changes: {
'Sheet1': {
1: {
1: {
text: 'Hello there'
}
}
}
}
})
}
return (
<div>
<button onClick={handleClick}>Update spreadsheet</button>
<SpreadSheet
ref={gridRef}
sheets={sheets}
onChange={setSheets}
autoFocus={false}
/>
</div>
)
}

Demo

Click button to update cell. Undo will be active

fx

Sheet1