Using react-table 7, editable cell re-renders on every change when using two-way binding to table's data source and I lose focus on the input

Viewed 888

Using react-table 7, my cell re-renders and loses focus on every change. I have it bound two ways to a property in its parent component. Inputting in the cell updates the data source that I'm using to populate the table, so it re-renders the table.

The official docs get around it in a pretty complicated way: The cell receives an updateMyData function which is passed to the table, the cell is defined with its own state so that onChange updates the cell's internal state, and onBlur runs the updateMyData function. This solution doesn't really work with how my project is structured, and with how I plan to re-use components.

Ideally I'd like to continue with the two-way binding approach that I have, but I'm out of ideas. The table data is already memoized but since it does change, it re-renders the table and I lose focus.

Here is my very simple cell definition:

    tempTable["defaultColumn"] = {
      Cell: ({value, row, column}) => {
                // TODO: fix this so that inputting doesn't render a new component, otherwise I lose focus on every change.
                return <input 
                    key={`Cell_${row}_${column}`} 
                    type="number" 
                    value={value} 
                    onChange={(e) => updateData(e, column, row)} />
            }
        }

My updateData function called onChange:

 const updateData = useCallback(
    (event, column, row) => {
      props.onProductTableChange(event, column, row);
    }, [props]
  );

and the onProductTableChange function defined in my parent component

    // handles changes to the events products table
    const handleProductTableChange =  useCallback(
        (event, column, row) => {
            // get the event products in a new array
            let newEventProducts = [...eventProducts];
            // let the value be the value of the event target
            let value = event.target.value
            // if the column we're updating is name, manually format some of the data differently... 
            if (column.id === "Name") {
                value = parseInt(event.target.value);
                newEventProducts[row.index].Id = value;
                // ... and manually get the new name, since we only updated the id
                const allProducts = [...ddvProducts];
                newEventProducts[row.index].Name = allProducts.filter(prod => prod.id === value)[0]["name"];
            } else {
                // else just set the column.id ("SalePrice", "QuantityAllocated", etc) to the new value
                newEventProducts[row.index][column.id] = value;
            }
            setEventProducts(newEventProducts);
            console.log(newEventProducts);
        }, 
        [setEventProducts, ddvProducts, eventProducts]
    );

Thanks in advance, I'd love to know if what I'm trying to do is even possible.

1 Answers

I ended up not being able to get around this, and had to manage state in each cell as in the example. I do want to share some code that I created for a cell with a select tag instead of a regular input tag:

import React from "react";

const SelectCell = ({ 
    row,
    column,
    value: initialValue,
    updateData,
    ddOptions
}) => {
    let options = <option key={"productLoading"} value={""}>Loading...</option>
    if (ddOptions) {
        options = ddOptions.map((prod) => (
      <option key={prod.id} value={prod.id}>
        {prod.name}
      </option>
    ));
    };
  return (
    <select value={initialValue} onChange={(e) => updateData(column, row, e.target.value)}>
      {options}
    </select>
  );
};

export default SelectCell;

Then defined my ddOptions on my table instance:

  const ddOptions = props.ddOptions; 
  const tableInstance = useTable(
    {
      columns,
            data,
            defaultColumn,
            updateData,
            ddOptions
    },
    useSortBy,
        usePagination,
        updateData,
        
  );

With ddOptions passed around from wherever you want in this format:

[
    {
        value: 1,
        name: "Test Name"
    },
    {
        value: 2,
        name: "Test Name 2"
    },
    {
        value: 3,
        name: "Test Name 3"
    },
]
Related