bvaughn / react-window

React components for efficiently rendering large lists and tabular data
https://react-window.now.sh/
MIT License
15.72k stars 782 forks source link

Input Loosing Focus When using react-window #629

Closed meilechwieder closed 2 years ago

meilechwieder commented 2 years ago

I'm using react-window together with react-table and when I type something in a cell, an then click on another cell, I have to click again to get the next cell focused so I can type. I don't think it has to do with key - the usual case of react loosing focus. Almost sure it has to do with react-window. When removing it, the issue is gone.

Focus is also being lost when I scroll with the mouse. So if I'm focused in a cell, and then scroll with the mouse wheel, it blures.

The render component is not created inline, it uses useCallback

Code Sandbox:

import React from 'react'
import { useTable } from 'react-table'
import { FixedSizeList } from 'react-window'

const EditableCell = ({
  value: initialValue,
  row: { index },
  column: { id },
  updateMyData,
}) => {
  const [value, setValue] = React.useState(initialValue)
  const onChange = e => setValue(e.target.value)
  const onBlur = () => updateMyData(index, id, value)
  React.useEffect(() => {setValue(initialValue)}, [initialValue])
  return <input value={value} onChange={onChange} onBlur={onBlur} />
}
const defaultColumn = { Cell: EditableCell,  width: 200,}
function Table({ columns, data, updateMyData }) {
  const table = useTable({columns, data, defaultColumn, updateMyData})

  const RenderRow = React.useCallback( ({ index, style }) => {
      const row = table.rows[index]
      table.prepareRow(row)
      return (
         <tr {...row.getRowProps({ style })} className="tr">
          {row.cells.map(cell =>  (
              <td {...cell.getCellProps()} className="td">
                {cell.render('Cell')}
              </td> ))}
        </tr> )
    }, [table.prepareRow, table.rows])

  return (
      <table {...table.getTableProps()}>
        <tbody {...table.getTableBodyProps()}>
        <FixedSizeList
          height={400}
          itemCount={table.rows.length}
          itemSize={35}
          width={table.totalColumnsWidth}
        >
          {RenderRow}
        </FixedSizeList>
        </tbody>
      </table>
  )
}

function App() {
  const columns = React.useMemo(
    () => [
          {Header: 'Name',accessor: 'name',},
        ],
    []
  )

  const [data, setData] = React.useState(() => {return Array(1000).fill({name:'Jack'})})

  const updateMyData = (rowIndex, columnId, value) => {
    const temp = [...data]
    temp[rowIndex]={...temp[rowIndex],[columnId]:value}
    setData(temp)
  }

  return ( <Table columns={columns} data={data} updateMyData={updateMyData} /> )
}

export default App
meilechwieder commented 2 years ago

Fixed by declaring the RenderItem component outside of the Table Component. So the component is not re-declared, but just the props are updating. (As recomended never to have child components declared inside Parent component)

Code Sandbox

import React from 'react'
import { useTable } from 'react-table'
import { FixedSizeList } from 'react-window'

const RenderRow = ({ index, style,data:{table} }) => {
  const row = table.rows[index]
  table.prepareRow(row)
  return (
     <tr {...row.getRowProps({ style })} className="tr">
      {row.cells.map(cell =>  (
          <td {...cell.getCellProps()} className="td">
            {cell.render('Cell')}
          </td> ))}
    </tr> )
}

const EditableCell = ({
  value: initialValue,
  row: { index },
  column: { id },
  updateMyData,
}) => {
  const [value, setValue] = React.useState(initialValue)
  const onChange = e => setValue(e.target.value)
  const onBlur = () => updateMyData(index, id, value)
  React.useEffect(() => {setValue(initialValue)}, [initialValue])
  return <input value={value} onChange={onChange} onBlur={onBlur} />
}
const defaultColumn = { Cell: EditableCell,  width: 200,}
function Table({ columns, data, updateMyData }) {
  const table = useTable({columns, data, defaultColumn, updateMyData})

  return (
      <table {...table.getTableProps()}>
        <tbody {...table.getTableBodyProps()}>
        <FixedSizeList
          height={400}
          itemData={{table}}
          itemCount={table.rows.length}
          itemSize={35}
          width={table.totalColumnsWidth}
        >
          {RenderRow}
        </FixedSizeList>
        </tbody>
      </table>
  )
}

function App() {
  const columns = React.useMemo(
    () => [
          {Header: 'Name',accessor: 'name',},
          {Header: 'Age',accessor: 'age',},
        ],
    []
  )

  const [data, setData] = React.useState(() => {return Array(1000).fill({name:'Jack'})})

  const updateMyData = (rowIndex, columnId, value) => {
    const temp = [...data]
    temp[rowIndex]={...temp[rowIndex],[columnId]:value}
    setData(temp)
  }

  return ( <Table columns={columns} data={data} updateMyData={updateMyData} /> )
}

export default App