I am using react-window
to optimize thousands of records from API.
The code works well except that the first column doesn’t scroll down with the Grid. Also, The overflow of records
should be the same with the Grid.
How to fix this?
Screenshots:
https://i.imgur.com/sxDeLpU.png
https://i.imgur.com/jPVWhVp.png
Here is my code:
import React, { useState, useCallback } from 'react';
import { FixedSizeGrid as Grid, GridChildComponentProps } from 'react-window';
import dayjs from 'dayjs';
interface Item {
unit_id: number;
unit_name: string;
}
interface Data {
id: number;
start_date: string;
end_date: string;
unit_id: number;
}
interface Cell {
row: number;
col: number;
}
// Generate the columns (dates of the current month)
const generateColumns = () => {
const startDate = dayjs().startOf('month');
const endDate = dayjs().endOf('month');
const columns: string[] = [];
for (
let date = startDate;
date.isBefore(endDate);
date = date.add(1, 'day')
) {
columns.push(date.format('YYYY-MM-DD'));
}
return columns;
};
const columns = generateColumns();
const generateItemList = (numItems: number) => {
const itemList: Item[] = [];
for (let i = 1; i <= numItems; i++) {
itemList.push({ unit_id: i, unit_name: `test${i}` });
}
return itemList;
};
const itemList = generateItemList(1000); // Generate 10 items
const data: Data[] = [
{
id: 4,
start_date: '2024-07-01',
end_date: '2024-07-06',
unit_id: 1,
},
{
id: 4,
start_date: '2024-07-03',
end_date: '2024-07-06',
unit_id: 2,
},
// Add more data as needed
];
// Helper function to check if a cell should be highlighted
const isCellHighlighted = (
rowIndex: number,
columnIndex: number,
itemList: Item[],
columns: string[],
data: Data[]
) => {
const unit_id = itemList[rowIndex].unit_id;
const date = columns[columnIndex];
return data.some(
(entry) =>
entry.unit_id === unit_id &&
dayjs(date).isBetween(entry.start_date, entry.end_date, null, '[]')
);
};
// Helper function to check if a cell should be disabled
const isCellDisabled = (
rowIndex: number,
columnIndex: number,
itemList: Item[],
columns: string[],
data: Data[]
) => {
const unit_id = itemList[rowIndex].unit_id;
const date = columns[columnIndex];
const isHighlighted = isCellHighlighted(
rowIndex,
columnIndex,
itemList,
columns,
data
);
return (
data.some(
(entry) => entry.unit_id === unit_id && date === entry.start_date
) || isHighlighted
);
};
const YourComponent: React.FC = () => {
const [selectedCells, setSelectedCells] = useState<Cell[]>([]);
const [isDragging, setIsDragging] = useState(false);
const [startCell, setStartCell] = useState<Cell | null>(null);
const handleMouseDown = useCallback(
(rowIndex: number, columnIndex: number) => {
const origin = { row: rowIndex, col: columnIndex };
setStartCell(origin);
setSelectedCells([origin]);
setIsDragging(true);
},
[]
);
const handleMouseEnter = useCallback(
(rowIndex: number, columnIndex: number) => {
if (isDragging && startCell) {
const newSelectedCells: Cell[] = [];
const startRow = Math.min(startCell.row, rowIndex);
const endRow = Math.max(startCell.row, rowIndex);
const startCol = Math.min(startCell.col, columnIndex);
const endCol = Math.max(startCell.col, columnIndex);
for (let row = startRow; row <= endRow; row++) {
for (let col = startCol; col <= endCol; col++) {
newSelectedCells.push({ row, col });
}
}
setSelectedCells(newSelectedCells);
}
},
[isDragging, startCell]
);
const handleMouseUp = useCallback(() => {
setIsDragging(false);
setStartCell(null);
}, []);
const handleButtonClick = useCallback(() => {
console.log('Selected Cells:', selectedCells);
}, [selectedCells]);
const cellRenderer: React.FC<GridChildComponentProps> = useCallback(
({ columnIndex, rowIndex, style }) => {
const isSelected = selectedCells.some(
(cell) => cell.row === rowIndex && cell.col === columnIndex
);
const isHighlighted = isCellHighlighted(
rowIndex,
columnIndex,
itemList,
columns,
data
);
const isDisabled = isCellDisabled(
rowIndex,
columnIndex,
itemList,
columns,
data
);
const cellStyle = {
...style,
backgroundColor: isSelected
? 'rgba(0, 123, 255, 0.5)'
: isHighlighted
? 'yellow'
: 'transparent',
border: '1px solid #ccc',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: isDisabled ? 'not-allowed' : 'pointer',
};
const handleMouseDownCell = isDisabled
? undefined
: () => handleMouseDown(rowIndex, columnIndex);
const handleMouseEnterCell = isDisabled
? undefined
: () => handleMouseEnter(rowIndex, columnIndex);
const handleMouseUpCell = isDisabled ? undefined : handleMouseUp;
return (
<div
style={cellStyle}
onMouseDown={handleMouseDownCell}
onMouseEnter={handleMouseEnterCell}
onMouseUp={handleMouseUpCell}
>
{rowIndex},{columnIndex}
</div>
);
},
[
columns,
handleMouseDown,
handleMouseEnter,
handleMouseUp,
itemList,
selectedCells,
]
);
return (
<div>
<button onClick={handleButtonClick}>Get Selected Cells</button>
<div style={{ maxWidth: '1000px', overflowX: 'auto' }} className="flex">
<div className="min-w-[300px] h-[500px] sticky left-0 bg-white z-20 mt-[25px]">
{itemList.map((item, index) => (
<div
key={index}
className="border h-[35px] border-neutral-500 flex items-center"
>
{item.unit_name}
</div>
))}
</div>
<div>
<div style={{ display: 'flex', width: columns.length * 100 + 100 }}>
<div
style={{
minWidth: 100,
backgroundColor: 'white',
border: '1px solid #ccc',
}}
></div>
{columns.map((col, index) => (
<div
key={index}
style={{
minWidth: 100,
backgroundColor: 'white',
border: '1px solid #ccc',
textAlign: 'center',
}}
>
{col}
</div>
))}
</div>
<Grid
columnCount={columns.length + 1} // +1 for the itemList column
columnWidth={100}
height={500}
rowCount={itemList.length}
rowHeight={35}
width={columns.length * 100 + 100}
>
{cellRenderer}
</Grid>
</div>
</div>
</div>
);
};
export default YourComponent;