Hello,
We recently were paged by an error that appears to be coming from Handsontable, and it looks like the row
value from the changes
object returned by afterChange
is either null
or undefined
. We have checks in place to ensure that the source
is not loadData
and that the changes
array is not null
, so I am confused as to how we could enter a state where the source is not “loadData”, the changes object is not null
and somehow a change within the changes array contains a null
or undefined row value.
Has anyone else encountered this or is anyone aware of a situation that might cause a null
row value in an otherwise valid changes
object?
Here is the TypeError we are getting:
Cannot read properties of undefined (reading 'parent_id')
For reference, here is our afterChange
implementation (please see the comment pointing out the line of the error):
const afterChange = (changes, source) => {
if (!source.includes("loadData") && changes) {
if (hotComponentRef) {
if (typeof props.updateFormSettingsDispatch === "function") {
// if it's on allocation tab, extra action is required to update the totals
if (isAllocationTargetConfigTab(props.strategicTargetTab)) {
// changes: [[row, rowProp, oldVal, newVal],...]
let invalidCount = 0;
changes.forEach((change) => {
const [row, col, oldVal, newVal] = change;
const tableData = tableSettings["data"];
const totalSumRowIndex = tableData.length - 1;
if (validateCellInput(newVal)) {
invalidCount++;
}
// moving totalCandidates into useMemo
// const totalCandidates = findTotalCandidates(tableData);
if (col > 1 && oldVal !== newVal) {
let columnValues = hotComponentRef.current.hotInstance.getData(
0,
col,
totalSumRowIndex,
col,
);
// if parent_id !== null, it should update the total of the parent asset class and the total.
// otherwise it should be an asset class, just update the total.
// Handsontable error triggers in the line below:
if (tableData[row]["parent_id"] != null) {
const parentIndex = findParentRowIndex(tableData, row);
const children = getChildren(tableData, parentIndex);
let [first, last] = findFirstAndLastIndex(children);
// const currentData = hotComponentRef.current.hotInstance.getData();
// getData(row, column, row2, column2)
const subAssetValues = hotComponentRef.current.hotInstance.getData(
first,
col,
last,
col,
);
// calculate sum of children row at col
const subAssetSum = calculateSum(subAssetValues);
// replace the cell value at (parentIndex, col) with calculated sum
hotComponentRef.current.hotInstance.setDataAtRowProp(parentIndex, col, subAssetSum);
// replace the columnValue at parentIndex of columnValues
columnValues[parentIndex] = subAssetSum;
} else {
// if parent_id === null, then skip and just update the total
}
// filter out only total candidates
const totalCandidateValues = columnValues.filter((value, i) =>
totalCandidates.includes(i),
);
// once the sum of asset classes gets updated, we should also update the column total.
// calculate sum of sum candidates row at col
const total = calculateSum(totalCandidateValues);
// replace the cell value at (parentIndex, col) with calculated sum
hotComponentRef.current.hotInstance.setDataAtRowProp(totalSumRowIndex, col, total);
}
});
if (invalidCount === 0) {
setInvalidInputError(false);
}
}
handleFormUpdate(duration);
}
} else {
console.warn("hotComponentRef was null.");
}
}
};
Thank you!