Skip to content
This repository has been archived by the owner on Mar 3, 2021. It is now read-only.

Commit

Permalink
Merged master
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Vaughn committed Dec 5, 2018
1 parent 3e9ff42 commit 8c60135
Show file tree
Hide file tree
Showing 21 changed files with 276 additions and 109 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
@@ -1,6 +1,21 @@
Changelog
------------

### 1.3.1
* 🎉 Pass `itemData` value to custom `itemKey` callbacks when present - [#90](https://github.com/bvaughn/react-window/issues/90))

### 1.3.0
* (Skipped)

### 1.2.4
* 🐛 Added Flow annotations to memoized methods to avoid a Flow warning for newer versions of Flow

### 1.2.3
* 🐛 Relaxed `children` validation checks. They were too strict and didn't support new React APIs like `memo`.

### 1.2.2
* 🐛 Improved Flow types for class component item renderers - ([nicholas-l](https://github.com/nicholas-l) - [#77](https://github.com/bvaughn/react-window/pull/77))

### 1.2.1
* 🎉 Improved Flow types to include optional `itemData` parameter. ([TrySound](https://github.com/TrySound) - [#66](https://github.com/bvaughn/react-window/pull/66))
* 🐛 `VariableSizeList` and `VariableSizeGrid` no longer call size getter functions with invalid index when item count is zero.
Expand Down
14 changes: 14 additions & 0 deletions README.md
Expand Up @@ -18,6 +18,20 @@ npm install --save react-window

Learn more at [react-window.now.sh](https://react-window.now.sh/).

## Frequently asked questions

#### How is `react-window` different from `react-virtualized`?
I wrote `react-virtualized` several years ago. At the time, I was new to both React and the concept of windowing. Because of this, I made a few API decisions that I later came to regret. One of these was adding too many non-essential features and components. Once you add something to an open source project, removing it is pretty painful for users.

`react-window` is a complete rewrite of `react-virtualized`. I didn't try to solve as many problems or support as many use cases. Instead I focused on making the package **smaller**<sup>1</sup> and **faster**. I also put a lot of thought into making the API (and documentation) as beginner-friendly as possible (with the caveat that windowing is still kind of an advanced use case).

If `react-window` provides the functionality your project needs, I would strongly recommend using it instead of `react-virtualized`. However if you need features that only `react-virtualized` provides, you have two options:

1. Use `react-virtualized`. (It's still widely used by a lot of successful projects!)
2. Create a component that decorates one of the `react-window` primitives and adds the functionality you need. You may even want to release this component to NPM (as its own, standalone package)! 🙂

<sup>1 - Adding a `react-virtualized` list to a CRA project increases the (gzipped) build size by ~33.5 KB. Adding a `react-window` list to a CRA project increases the (gzipped) build size by &lt;2 KB.</sup>

## License

MIT © [bvaughn](https://github.com/bvaughn)
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "react-window",
"version": "1.3.0-alpha.0",
"version": "1.3.1",
"description":
"React components for efficiently rendering large, scrollable lists and tabular data",
"author":
Expand Down
5 changes: 3 additions & 2 deletions src/DynamicSizeList.js
Expand Up @@ -327,10 +327,11 @@ const DynamicSizeList = createListComponent({
const { scrollOffset } = instance.state;
const { direction } = instance.props;

const element = ((instance._outerRef: any): HTMLDivElement);
if (direction === 'horizontal') {
((instance._outerRef: any): HTMLDivElement).scrollLeft = scrollOffset;
element.scrollLeft = scrollOffset;
} else {
((instance._outerRef: any): HTMLDivElement).scrollTop = scrollOffset;
element.scrollTop = scrollOffset;
}
}

Expand Down
22 changes: 20 additions & 2 deletions src/__tests__/FixedSizeGrid.js
Expand Up @@ -561,6 +561,24 @@ describe('FixedSizeGrid', () => {
expect(keyMapItemRenderer.mock.calls[1][0].columnIndex).toBe(2);
expect(keyMapItemRenderer.mock.calls[1][0].rowIndex).toBe(1);
});

it('should receive a data value if itemData is provided', () => {
const itemKey = jest.fn(
({ columnIndex, data, rowIndex }) => `${columnIndex}-${rowIndex}`
);
const itemData = {};
ReactTestRenderer.create(
<FixedSizeGrid
{...defaultProps}
itemData={itemData}
itemKey={itemKey}
/>
);
expect(itemKey).toHaveBeenCalled();
expect(
itemKey.mock.calls.filter(([params]) => params.data === itemData)
).toHaveLength(itemKey.mock.calls.length);
});
});

describe('refs', () => {
Expand Down Expand Up @@ -678,14 +696,14 @@ describe('FixedSizeGrid', () => {
);
});

it('should fail if no children function is provided', () => {
it('should fail if no children value is provided', () => {
expect(() =>
ReactTestRenderer.create(
<FixedSizeGrid {...defaultProps} children={undefined} />
)
).toThrow(
'An invalid "children" prop has been specified. ' +
'Value should be a function that creates a React element. ' +
'Value should be a React component. ' +
'"undefined" was specified.'
);
});
Expand Down
20 changes: 18 additions & 2 deletions src/__tests__/FixedSizeList.js
Expand Up @@ -471,6 +471,22 @@ describe('FixedSizeList', () => {
expect(keyMapItemRenderer.mock.calls[0][0].index).toBe(0);
expect(keyMapItemRenderer.mock.calls[1][0].index).toBe(2);
});

it('should receive a data value if itemData is provided', () => {
const itemKey = jest.fn(index => index);
const itemData = {};
ReactTestRenderer.create(
<FixedSizeList
{...defaultProps}
itemData={itemData}
itemKey={itemKey}
/>
);
expect(itemKey).toHaveBeenCalled();
expect(
itemKey.mock.calls.filter(([index, data]) => data === itemData)
).toHaveLength(itemKey.mock.calls.length);
});
});

describe('refs', () => {
Expand Down Expand Up @@ -576,14 +592,14 @@ describe('FixedSizeList', () => {
);
});

it('should fail if no children function is provided', () => {
it('should fail if no children value is provided', () => {
expect(() =>
ReactTestRenderer.create(
<FixedSizeList {...defaultProps} children={undefined} />
)
).toThrow(
'An invalid "children" prop has been specified. ' +
'Value should be a function that creates a React element. ' +
'Value should be a React component. ' +
'"undefined" was specified.'
);
});
Expand Down
24 changes: 13 additions & 11 deletions src/createGridComponent.js
Expand Up @@ -6,10 +6,6 @@ import { createElement, PureComponent } from 'react';
export type ScrollToAlign = 'auto' | 'center' | 'start' | 'end';

type itemSize = number | ((index: number) => number);
type ItemKeyGetter = (indices: {
columnIndex: number,
rowIndex: number,
}) => any;

type RenderComponentProps<T> = {|
columnIndex: number,
Expand All @@ -18,7 +14,9 @@ type RenderComponentProps<T> = {|
rowIndex: number,
style: Object,
|};
export type RenderComponent<T> = (props: RenderComponentProps<T>) => React$Node;
export type RenderComponent<T> = React$ComponentType<
$Shape<RenderComponentProps<T>>
>;

type ScrollDirection = 'forward' | 'backward';

Expand Down Expand Up @@ -54,7 +52,11 @@ export type Props<T> = {|
innerRef?: any,
innerTagName?: string,
itemData: T,
itemKey?: ItemKeyGetter,
itemKey?: (params: {|
columnIndex: number,
data: T,
rowIndex: number,
|}) => any,
onItemsRendered?: OnItemsRenderedCallback,
onScroll?: OnScrollCallback,
outerRef?: any,
Expand Down Expand Up @@ -110,7 +112,7 @@ type ValidateProps = (props: Props<any>) => void;

const IS_SCROLLING_DEBOUNCE_INTERVAL = 150;

const defaultItemKey: ItemKeyGetter = ({ columnIndex, rowIndex }) =>
const defaultItemKey = ({ columnIndex, data, rowIndex }) =>
`${rowIndex}:${columnIndex}`;

export default function createGridComponent({
Expand Down Expand Up @@ -315,7 +317,7 @@ export default function createGridComponent({
columnIndex,
data: itemData,
isScrolling: useIsScrolling ? isScrolling : undefined,
key: itemKey({ columnIndex, rowIndex }),
key: itemKey({ columnIndex, data: itemData, rowIndex }),
rowIndex,
style: this._getItemStyle(rowIndex, columnIndex),
})
Expand Down Expand Up @@ -501,7 +503,7 @@ export default function createGridComponent({
// If all that's really needed is for the impl to be able to reset the cache,
// Then we could expose a better API for that.
_getItemStyleCache: (_: any, __: any) => ItemStyleCache;
_getItemStyleCache = memoizeOne((_, __) => ({}));
_getItemStyleCache = memoizeOne((_: any, __: any) => ({}));

_getHorizontalRangeToRender(): [number, number, number, number] {
const { columnCount, overscanCount, rowCount } = this.props;
Expand Down Expand Up @@ -655,10 +657,10 @@ export default function createGridComponent({

const validateSharedProps = ({ children, height, width }: Props<any>): void => {
if (process.env.NODE_ENV !== 'production') {
if (typeof children !== 'function') {
if (children == null) {
throw Error(
'An invalid "children" prop has been specified. ' +
'Value should be a function that creates a React element. ' +
'Value should be a React component. ' +
`"${children === null ? 'null' : typeof children}" was specified.`
);
}
Expand Down
25 changes: 12 additions & 13 deletions src/createListComponent.js
Expand Up @@ -7,15 +7,14 @@ export type ScrollToAlign = 'auto' | 'center' | 'start' | 'end';

type itemSize = number | ((index: number) => number);
export type Direction = 'horizontal' | 'vertical';
type ItemKeyGetter = (index: number) => any;

export type RenderComponentProps<T> = {|
data: T,
index: number,
isScrolling?: boolean,
style: Object,
|};
type RenderComponent<T> = (props: RenderComponentProps<T>) => React$Node;
type RenderComponent<T> = React$ComponentType<$Shape<RenderComponentProps<T>>>;

type ScrollDirection = 'forward' | 'backward';

Expand Down Expand Up @@ -44,7 +43,7 @@ export type Props<T> = {|
innerTagName?: string,
itemCount: number,
itemData: T,
itemKey?: ItemKeyGetter,
itemKey?: (index: number, data: T) => any,
itemSize: itemSize,
onItemsRendered?: onItemsRenderedCallback,
onScroll?: onScrollCallback,
Expand Down Expand Up @@ -97,7 +96,7 @@ type ValidateProps = (props: Props<any>) => void;

const IS_SCROLLING_DEBOUNCE_INTERVAL = 150;

export const defaultItemKey: ItemKeyGetter = index => index;
export const defaultItemKey = (index: number, data: any) => index;

export default function createListComponent({
getItemOffset,
Expand Down Expand Up @@ -189,12 +188,11 @@ export default function createListComponent({
const { initialScrollOffset, direction } = this.props;

if (typeof initialScrollOffset === 'number' && this._outerRef !== null) {
const element = ((this._outerRef: any): HTMLDivElement);
if (direction === 'horizontal') {
((this
._outerRef: any): HTMLDivElement).scrollLeft = initialScrollOffset;
element.scrollLeft = initialScrollOffset;
} else {
((this
._outerRef: any): HTMLDivElement).scrollTop = initialScrollOffset;
element.scrollTop = initialScrollOffset;
}
}

Expand All @@ -207,10 +205,11 @@ export default function createListComponent({
const { scrollOffset, scrollUpdateWasRequested } = this.state;

if (scrollUpdateWasRequested && this._outerRef !== null) {
const element = ((this._outerRef: any): HTMLDivElement);
if (direction === 'horizontal') {
((this._outerRef: any): HTMLDivElement).scrollLeft = scrollOffset;
element.scrollLeft = scrollOffset;
} else {
((this._outerRef: any): HTMLDivElement).scrollTop = scrollOffset;
element.scrollTop = scrollOffset;
}
}

Expand Down Expand Up @@ -466,7 +465,7 @@ export default function createListComponent({
items.push(
createElement(children, {
data: itemData,
key: itemKey(index),
key: itemKey(index, itemData),
index,
isScrolling: useIsScrolling ? isScrolling : undefined,
style: this._getItemStyle(index),
Expand Down Expand Up @@ -581,10 +580,10 @@ const validateSharedProps = ({
);
}

if (typeof children !== 'function') {
if (children == null) {
throw Error(
'An invalid "children" prop has been specified. ' +
'Value should be a function that creates a React element. ' +
'Value should be a React component. ' +
`"${children === null ? 'null' : typeof children}" was specified.`
);
}
Expand Down

0 comments on commit 8c60135

Please sign in to comment.