Skip to content

Commit

Permalink
FixedSizeGrid and FixedSizeList automatically clear style cache when …
Browse files Browse the repository at this point in the history
…item size props change.
  • Loading branch information
Brian Vaughn committed Aug 6, 2018
1 parent 6660f83 commit 8c6ec40
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 19 deletions.
2 changes: 2 additions & 0 deletions src/FixedSizeGrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ const FixedSizeGrid = createGridComponent({
// Noop
},

shouldResetStyleCacheOnItemSizeChange: true,

validateProps: ({ columnWidth, rowHeight }: Props): void => {
if (process.env.NODE_ENV !== 'production') {
if (typeof columnWidth !== 'number') {
Expand Down
2 changes: 2 additions & 0 deletions src/FixedSizeList.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ const FixedSizeList = createListComponent({
// Noop
},

shouldResetStyleCacheOnItemSizeChange: true,

validateProps: ({ itemSize }: Props): void => {
if (process.env.NODE_ENV !== 'production') {
if (typeof itemSize !== 'number') {
Expand Down
4 changes: 3 additions & 1 deletion src/VariableSizeGrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ const VariableSizeGrid = createGridComponent({
// But since styles are only cached while scrolling is in progress-
// It seems an unnecessary optimization.
// It's unlikely that resetAfterIndex() will be called while a user is scrolling.
instance._itemStyleCache = {};
instance._getItemStyleCache(-1);

if (shouldForceUpdate) {
instance.forceUpdate();
Expand All @@ -446,6 +446,8 @@ const VariableSizeGrid = createGridComponent({
return instanceProps;
},

shouldResetStyleCacheOnItemSizeChange: false,

validateProps: ({ columnWidth, rowHeight }: Props): void => {
if (process.env.NODE_ENV !== 'production') {
if (typeof columnWidth !== 'function') {
Expand Down
4 changes: 3 additions & 1 deletion src/VariableSizeList.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ const VariableSizeList = createListComponent({
// But since styles are only cached while scrolling is in progress-
// It seems an unnecessary optimization.
// It's unlikely that resetAfterIndex() will be called while a user is scrolling.
instance._itemStyleCache = {};
instance._getItemStyleCache(-1);

if (shouldForceUpdate) {
instance.forceUpdate();
Expand All @@ -274,6 +274,8 @@ const VariableSizeList = createListComponent({
return instanceProps;
},

shouldResetStyleCacheOnItemSizeChange: false,

validateProps: ({ itemSize }: Props): void => {
if (process.env.NODE_ENV !== 'production') {
if (typeof itemSize !== 'function') {
Expand Down
18 changes: 18 additions & 0 deletions src/__tests__/FixedSizeGrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,24 @@ describe('FixedSizeGrid', () => {
expect(onItemsRendered.mock.calls).toMatchSnapshot();
});

it('changing itemSize updates the rendered items and busts the style cache', () => {
const rendered = ReactTestRenderer.create(
<FixedSizeGrid {...defaultProps} />
);
const styleOne = itemRenderer.mock.calls[0][0].style;
itemRenderer.mockClear();
rendered.update(<FixedSizeGrid {...defaultProps} columnWidth={150} />);
expect(itemRenderer).toHaveBeenCalled();
const styleTwo = itemRenderer.mock.calls[0][0].style;
expect(styleOne).not.toBe(styleTwo);
itemRenderer.mockClear();
rendered.update(
<FixedSizeGrid {...defaultProps} columnWidth={150} rowHeight={50} />
);
const styleThree = itemRenderer.mock.calls[0][0].style;
expect(styleTwo).not.toBe(styleThree);
});

it('should support momentum scrolling on iOS devices', () => {
const rendered = ReactTestRenderer.create(
<FixedSizeGrid {...defaultProps} />
Expand Down
12 changes: 12 additions & 0 deletions src/__tests__/FixedSizeList.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,18 @@ describe('FixedSizeList', () => {
expect(onItemsRendered.mock.calls).toMatchSnapshot();
});

it('changing itemSize updates the rendered items and busts the style cache', () => {
const rendered = ReactTestRenderer.create(
<FixedSizeList {...defaultProps} />
);
const oldStyle = itemRenderer.mock.calls[0][0].style;
itemRenderer.mockClear();
rendered.update(<FixedSizeList {...defaultProps} itemSize={50} />);
expect(itemRenderer).toHaveBeenCalled();
const newStyle = itemRenderer.mock.calls[0][0].style;
expect(oldStyle).not.toBe(newStyle);
});

it('should support momentum scrolling on iOS devices', () => {
const rendered = ReactTestRenderer.create(
<FixedSizeList {...defaultProps} />
Expand Down
20 changes: 15 additions & 5 deletions src/createGridComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type OnScrollCallback = ({
}) => void;

type ScrollEvent = SyntheticEvent<HTMLDivElement>;
type ItemStyleCache = { [key: string]: Object };

export type Props = {|
children: RenderComponent,
Expand Down Expand Up @@ -122,6 +123,7 @@ export default function createGridComponent({
getRowStartIndexForOffset,
getRowStopIndexForStartIndex,
initInstanceProps,
shouldResetStyleCacheOnItemSizeChange,
validateProps,
}: {|
getColumnOffset: getItemOffset,
Expand All @@ -137,11 +139,11 @@ export default function createGridComponent({
getRowStartIndexForOffset: GetStartIndexForOffset,
getRowStopIndexForStartIndex: GetStopIndexForStartIndex,
initInstanceProps: InitInstanceProps,
shouldResetStyleCacheOnItemSizeChange: boolean,
validateProps: ValidateProps,
|}) {
return class Grid extends PureComponent<Props, State> {
_instanceProps: any = initInstanceProps(this.props, this);
_itemStyleCache: { [key: string]: Object } = {};
_resetIsScrollingTimeoutId: TimeoutID | null = null;
_outerRef: ?HTMLDivElement;

Expand Down Expand Up @@ -464,11 +466,16 @@ export default function createGridComponent({
_getItemStyle = (rowIndex: number, columnIndex: number): Object => {
const key = `${rowIndex}:${columnIndex}`;

const itemStyleCache = this._getItemStyleCache(
shouldResetStyleCacheOnItemSizeChange && this.props.columnWidth,
shouldResetStyleCacheOnItemSizeChange && this.props.rowHeight
);

let style;
if (this._itemStyleCache.hasOwnProperty(key)) {
style = this._itemStyleCache[key];
if (itemStyleCache.hasOwnProperty(key)) {
style = itemStyleCache[key];
} else {
this._itemStyleCache[key] = style = {
itemStyleCache[key] = style = {
position: 'absolute',
left: getColumnOffset(this.props, columnIndex, this._instanceProps),
top: getRowOffset(this.props, rowIndex, this._instanceProps),
Expand All @@ -480,6 +487,9 @@ export default function createGridComponent({
return style;
};

_getItemStyleCache: (_: any, __: any) => ItemStyleCache;
_getItemStyleCache = memoizeOne((_, __) => ({}));

_getHorizontalRangeToRender(): [number, number, number, number] {
const { columnCount, overscanCount } = this.props;
const { horizontalScrollDirection, scrollLeft } = this.state;
Expand Down Expand Up @@ -616,7 +626,7 @@ export default function createGridComponent({
this.setState({ isScrolling: false }, () => {
// Clear style cache after state update has been committed.
// This way we don't break pure sCU for items that don't use isScrolling param.
this._itemStyleCache = {};
this._getItemStyleCache(-1);
});
};
};
Expand Down
30 changes: 18 additions & 12 deletions src/createListComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type onScrollCallback = ({
}) => void;

type ScrollEvent = SyntheticEvent<HTMLDivElement>;
type ItemStyleCache = { [index: number]: Object };

export type Props = {|
children: RenderComponent,
Expand Down Expand Up @@ -102,6 +103,7 @@ export default function createListComponent({
getStartIndexForOffset,
getStopIndexForStartIndex,
initInstanceProps,
shouldResetStyleCacheOnItemSizeChange,
validateProps,
}: {|
getItemOffset: GetItemOffset,
Expand All @@ -111,11 +113,11 @@ export default function createListComponent({
getStartIndexForOffset: GetStartIndexForOffset,
getStopIndexForStartIndex: GetStopIndexForStartIndex,
initInstanceProps: InitInstanceProps,
shouldResetStyleCacheOnItemSizeChange: boolean,
validateProps: ValidateProps,
|}) {
return class List extends PureComponent<Props, State> {
_instanceProps: any = initInstanceProps(this.props, this);
_itemStyleCache: { [index: number]: Object } = {};
_outerRef: ?HTMLDivElement;
_resetIsScrollingTimeoutId: TimeoutID | null = null;

Expand Down Expand Up @@ -144,12 +146,9 @@ export default function createListComponent({
super(props);
}

static getDerivedStateFromProps(
nextProps: Props,
prevState: State
): $Shape<State> {
validateSharedProps(nextProps);
validateProps(nextProps);
static getDerivedStateFromProps(props: Props, state: State): $Shape<State> {
validateSharedProps(props);
validateProps(props);
return null;
}

Expand Down Expand Up @@ -369,13 +368,17 @@ export default function createListComponent({
// So that List can clear cached styles and force item re-render if necessary.
_getItemStyle: (index: number) => Object;
_getItemStyle = (index: number): Object => {
const { direction } = this.props;
const { direction, itemSize } = this.props;

const itemStyleCache = this._getItemStyleCache(
shouldResetStyleCacheOnItemSizeChange && itemSize
);

let style;
if (this._itemStyleCache.hasOwnProperty(index)) {
style = this._itemStyleCache[index];
if (itemStyleCache.hasOwnProperty(index)) {
style = itemStyleCache[index];
} else {
this._itemStyleCache[index] = style = {
itemStyleCache[index] = style = {
position: 'absolute',
left:
direction === 'horizontal'
Expand All @@ -399,6 +402,9 @@ export default function createListComponent({
return style;
};

_getItemStyleCache: (_: any) => ItemStyleCache;
_getItemStyleCache = memoizeOne(_ => ({}));

_getRangeToRender(): [number, number, number, number] {
const { itemCount, overscanCount } = this.props;
const { scrollDirection, scrollOffset } = this.state;
Expand Down Expand Up @@ -503,7 +509,7 @@ export default function createListComponent({
this.setState({ isScrolling: false }, () => {
// Clear style cache after state update has been committed.
// This way we don't break pure sCU for items that don't use isScrolling param.
this._itemStyleCache = {};
this._getItemStyleCache(-1);
});
};
};
Expand Down

0 comments on commit 8c6ec40

Please sign in to comment.