Open kraenhansen opened 6 years ago
Similar to #315 🤔
I'll update this by mentioning that I've got this to work by implementing a cellRangeRenderer
callback on the grid https://github.com/bvaughn/react-virtualized/blob/master/docs/Grid.md
I think it would be awesome with an example on this lib that shows that.
Here's my implementation (rowCellRangeRenderer.tsx) which needs to be updated to do better caching of the rows:
import * as React from 'react';
import {
Grid,
GridCellProps,
GridCellRangeProps,
List,
Table,
} from 'react-virtualized';
/**
* Default implementation of cellRangeRenderer used by Grid.
* This renderer supports cell-caching while the user is scrolling.
*/
export interface IGridRowProps {
children: React.ReactNode[];
isScrolling: boolean;
isVisible: boolean;
key: string;
parent: Grid | List | Table;
rowIndex: number;
style: React.CSSProperties;
}
export type GridRowRenderer = (props: IGridRowProps) => JSX.Element;
export const rowCellRangeRenderer = (rowRenderer: GridRowRenderer) => ({
cellCache,
cellRenderer,
columnSizeAndPositionManager,
columnStartIndex,
columnStopIndex,
deferredMeasurementCache,
horizontalOffsetAdjustment,
isScrolling,
parent, // Grid (or List or Table)
rowSizeAndPositionManager,
rowStartIndex,
rowStopIndex,
styleCache,
verticalOffsetAdjustment,
visibleColumnIndices,
visibleRowIndices,
}: GridCellRangeProps) => {
const renderedRows: React.ReactNode[] = [];
// Browsers have native size limits for elements (eg Chrome 33M pixels, IE 1.5M pixes).
// User cannot scroll beyond these size limitations.
// In order to work around this, ScalingCellSizeAndPositionManager compresses offsets.
// We should never cache styles for compressed offsets though as this can lead to bugs.
// See issue #576 for more.
const areOffsetsAdjusted =
columnSizeAndPositionManager.areOffsetsAdjusted() ||
rowSizeAndPositionManager.areOffsetsAdjusted();
const canCacheStyle = !isScrolling && !areOffsetsAdjusted;
for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) {
const rowDatum = rowSizeAndPositionManager.getSizeAndPositionOfCell(
rowIndex,
);
const renderedCells: React.ReactNode[] = [];
const rowKey = `${rowIndex}`;
for (
let columnIndex = columnStartIndex;
columnIndex <= columnStopIndex;
columnIndex++
) {
const columnDatum = columnSizeAndPositionManager.getSizeAndPositionOfCell(
columnIndex,
);
const isCellVisible =
columnIndex >= visibleColumnIndices.start &&
columnIndex <= visibleColumnIndices.stop &&
rowIndex >= visibleRowIndices.start &&
rowIndex <= visibleRowIndices.stop;
const cellKey = `${rowIndex}-${columnIndex}`;
let cellStyle: React.CSSProperties;
// Cache style objects so shallow-compare doesn't re-render unnecessarily.
if (canCacheStyle && styleCache[cellKey]) {
cellStyle = styleCache[cellKey];
} else {
// In deferred mode, cells will be initially rendered before we know their size.
// Don't interfere with CellMeasurer's measurements by setting an invalid size.
if (
deferredMeasurementCache &&
!deferredMeasurementCache.has(rowIndex, columnIndex)
) {
// Position not-yet-measured cells at top/left 0,0,
// And give them width/height of 'auto' so they can grow larger than the parent Grid if necessary.
// Positioning them further to the right/bottom influences their measured size.
cellStyle = {
height: 'auto',
left: 0,
position: 'absolute',
top: 0,
width: 'auto',
};
} else {
cellStyle = {
height: rowDatum.size,
left: columnDatum.offset + horizontalOffsetAdjustment,
position: 'absolute',
top: 0,
width: columnDatum.size,
};
styleCache[cellKey] = cellStyle;
}
}
const cellRendererParams: GridCellProps = {
columnIndex,
isScrolling,
isVisible: isCellVisible,
key: cellKey,
parent,
rowIndex,
style: cellStyle,
};
let renderedCell: React.ReactNode;
// Avoid re-creating cells while scrolling.
// This can lead to the same cell being created many times and can cause performance issues for "heavy" cells.
// If a scroll is in progress- cache and reuse cells.
// This cache will be thrown away once scrolling completes.
// However if we are scaling scroll positions and sizes, we should also avoid caching.
// This is because the offset changes slightly as scroll position changes and caching leads to stale values.
// For more info refer to issue #395
if (
isScrolling &&
!horizontalOffsetAdjustment &&
!verticalOffsetAdjustment
) {
if (!cellCache[cellKey]) {
cellCache[cellKey] = cellRenderer(cellRendererParams);
}
renderedCell = cellCache[cellKey];
// If the user is no longer scrolling, don't cache cells.
// This makes dynamic cell content difficult for users and would also lead to a heavier memory footprint.
} else {
renderedCell = cellRenderer(cellRendererParams);
}
if (renderedCell == null || renderedCell === false) {
continue;
}
if (process.env.NODE_ENV !== 'production') {
warnAboutMissingStyle(parent, renderedCell);
}
renderedCells.push(renderedCell);
}
const isRowVisible =
rowIndex >= visibleRowIndices.start && rowIndex <= visibleRowIndices.stop;
let rowStyle: React.CSSProperties;
// Cache style objects so shallow-compare doesn't re-render unnecessarily.
if (canCacheStyle && styleCache[rowKey]) {
rowStyle = styleCache[rowKey];
} else {
rowStyle = {
height: rowDatum.size,
left: 0,
position: 'absolute',
right: 0,
top: rowDatum.offset + verticalOffsetAdjustment,
};
styleCache[rowKey] = rowStyle;
}
const rowRendererParams: IGridRowProps = {
children: renderedCells,
isScrolling,
isVisible: isRowVisible,
key: rowKey,
parent,
rowIndex,
style: rowStyle,
};
// The cache block is commented out - as caching rows when scrolling horizontally wont
// render correctly when scrolling to new columns.
// If re-enabled, its cache key should contain the range of column indecies that it shows.
const renderedRow = rowRenderer(rowRendererParams);
// Avoid re-creating cells while scrolling.
// This can lead to the same cell being created many times and can cause performance issues for "heavy" cells.
// If a scroll is in progress- cache and reuse cells.
// This cache will be thrown away once scrolling completes.
// However if we are scaling scroll positions and sizes, we should also avoid caching.
// This is because the offset changes slightly as scroll position changes and caching leads to stale values.
// For more info refer to issue #395
/*
if (
isScrolling &&
!horizontalOffsetAdjustment &&
!verticalOffsetAdjustment
) {
if (!cellCache[rowKey]) {
cellCache[rowKey] = rowRenderer(rowRendererParams);
}
renderedRow = cellCache[rowKey];
// If the user is no longer scrolling, don't cache cells.
// This makes dynamic cell content difficult for users and would also lead to a heavier memory footprint.
} else {
renderedRow = rowRenderer(rowRendererParams);
}
*/
if (renderedRow == null) {
continue;
}
if (process.env.NODE_ENV !== 'production') {
warnAboutMissingStyle(parent, renderedRow);
}
renderedRows.push(renderedRow);
}
return renderedRows;
};
function warnAboutMissingStyle(parent: any, renderedCell: any) {
if (process.env.NODE_ENV !== 'production') {
if (renderedCell) {
// If the direct child is a CellMeasurer, then we should check its child
// See issue #611
if (renderedCell.type && renderedCell.type.__internalCellMeasurerFlag) {
renderedCell = renderedCell.props.children;
}
if (
renderedCell &&
renderedCell.props &&
renderedCell.props.style === undefined &&
parent.__warnedAboutMissingStyle !== true
) {
parent.__warnedAboutMissingStyle = true;
// tslint:disable-next-line:no-console
console.warn(
'Rendered cell should include style property for positioning.',
);
}
}
}
}
Hi guys, I've the same problem, but I can't find good solution, maybe someone fixed this ?
@R-iskey - did you see my implementation above? ☝️ To my knowledge, thats currently the best way to do it.
@kraenhansen, thanks for sharing your rowCellRangeRenderer
implementation. Can you also share how you tie it all together? How do you integrate Grid
with SortableContainer
, SortableElement
?
First of all - this looks like an awesome project, thank you for that!
I want to use it with react-virtualized's Grid where the rows are sortable. I cannot use the List as I have multiple columns and I cannot use it with the Table because I have too many columns and therefore need horizontal scrolling.
The issue that I am facing is that the Grid does not have a
rowRenderer
callback as the Table or List has (it only hascellRenderer
andcellRenderer
), thus I don't know what to wrap theSortableElement
around. Do you have any ideas or have you seen any examples of this usage?