react-spreadsheet icon indicating copy to clipboard operation
react-spreadsheet copied to clipboard

Infinite rerender loop triggered with controlled Spreadsheet

Open aioobe opened this issue 1 year ago • 5 comments

Reproducer:

yarn create vite spreadsheet-repro --template react-ts
yarn add scheduler react-spreadsheet
yarn
yarn dev

Then replace the code in App.tsx with the following:

  const [data, setData] = useState([
    [{ value: "Vanilla" }, { value: "Chocolate" }, { value: "" }],
    [{ value: "Strawberry" }, { value: "Cookies" }, { value: "" }],
  ]);

  const setDataWrapper = (data) => {
      console.log("setting data")
      setData(data)
  }      

  return (
    <Spreadsheet data={data} onChange={setDataWrapper} />
  )

Start editing cells and the console will be spammed with hundreds of rapid setting data lines. (It might take two or three edits, but it always happens consistently in both Firefox and Chrome.

Using react-spreadsheet 0.9.5 but have tried multiple versions ranging back to 0.7.0.

aioobe avatar Sep 11 '24 12:09 aioobe

Experiencing the same issue!

dennis-meitner avatar Sep 11 '24 12:09 dennis-meitner

Same!

oskarscholander avatar Sep 11 '24 12:09 oskarscholander

Same issue here!

Vinicius-V-Rodrigues avatar Sep 20 '24 13:09 Vinicius-V-Rodrigues

Here's a temporary workaround which worked for me whilst we wait for a merge.

import _ from 'lodash';
import { useCallback, useEffect, useRef, useState } from 'react';
import ReactSpreadsheet from 'react-spreadsheet';

/**
 * A component for displaying a spreadsheet.
 * @param data
 * @param onChange
 * @returns {JSX.Element}
 * @constructor
 */
export default function Spreadsheet({ data, onChange }) {
    const previousData = useRef(data);
    const [spreadsheetData, setSpreadsheetData] = useState(data);

    /**
     * Handle the change of the spreadsheet data.
     * @type {(function(*): void)|*}
     */
    const handleSpreadsheetChange = useCallback(updatedData => {
        // If the data is different to the previous update, set the spreadsheet data.
        if (!_.isEqual(spreadsheetData, previousData.current)) {
            setSpreadsheetData(updatedData);

            // If there's a callback passed, then run it./
            if (onChange) {
                onChange(updatedData);
            }
        }

        // Update the previous data ref.
        previousData.current = updatedData;
    }, [spreadsheetData, previousData, onChange]);

    useEffect(() => {
        console.log('update');
    }, [spreadsheetData]);

    return <ReactSpreadsheet data={spreadsheetData} onChange={handleSpreadsheetChange} />;
}

danielgoodwin97 avatar Nov 17 '24 01:11 danielgoodwin97

@danielgoodwin97 is the solution currently and has fixed the issue I was having.

However, I think that installing the entire lodash package is quite overkill when you only need one function from it.

You can achieve the same functionality by using react-fast-compare

They provide an isEqual function that can be imported in via your usual es6 import:

import isEqual from "react-fast-compare";

JamesDavis7 avatar Jan 30 '25 13:01 JamesDavis7