iddan / react-spreadsheet

Simple, customizable yet performant spreadsheet for React
https://iddan.github.io/react-spreadsheet
MIT License
1.34k stars 157 forks source link

Infinite rerender loop triggered with controlled Spreadsheet #410

Open aioobe opened 2 months ago

aioobe commented 2 months ago

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.

dennis-meitner commented 2 months ago

Experiencing the same issue!

oskarscholander commented 2 months ago

Same!

ViniciusValler-Rapidsoft commented 2 months ago

Same issue here!

danielgoodwin97 commented 2 weeks ago

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} />;
}