Search code examples
javascriptreactjsinputreact-tablereact-window

React-window loses focus


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.

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

Solution

  • 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