Skip to content

Commit de19504

Browse files
author
Luis Merino
committed
feat(currentLength): replace itemsLength with more explicit currentLength prop name
Warns when using itemsLength instead of currentLength. Some users had difficulties with the meaning of the former prop name and interpreted it as totalLength which led them to misuse the component's awaitMore prop.
1 parent f6868fb commit de19504

File tree

8 files changed

+69
-54
lines changed

8 files changed

+69
-54
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export default class MyList extends React.Component {
4949

5050
render() {
5151
return (
52-
<List itemsLength={1000} itemsRenderer={this.itemsRenderer}>
52+
<List currentLength={1000} itemsRenderer={this.itemsRenderer}>
5353
{this.itemRenderer}
5454
</List>
5555
);
@@ -85,7 +85,7 @@ Provided an `itemsRenderer` prop you must attach the `ref` argument to your scro
8585

8686
This element specifies `overflow: auto|scroll` and it'll become the `IntersectionObserver root`. If the `overflow` property isn't found, then `window` will be used as the `root` instead.
8787

88-
The `<sentinel />` element is by default detached from the list when the current size reaches the available length, unless you're using `awaitMore`. In case your list is in memory and you rely on the list for incremental rendering only, the default detaching behavior suffices. If you're loading items asynchoronously on-demand, make sure to switch `awaitMore` once you reach the total `itemsLength`.
88+
The `<sentinel />` element is by default detached from the list when the current size reaches the available length, unless you're using `awaitMore`. In case your list is in memory and you rely on the list for incremental rendering only, the default detaching behavior suffices. If you're loading items asynchoronously on-demand, make sure to switch `awaitMore` once you reach the total length.
8989

9090
### FAQ
9191

@@ -113,7 +113,7 @@ The prop `pageSize` is `10` by default, so make sure you're not falling short on
113113

114114
- **itemsRenderer**: `(items: Array<React.Element<*>>, ref: HTMLElement) => React.Element<*>`
115115

116-
- **itemsLength**: `number` | default: `0` (number of renderable items)
116+
- **currentLength**: `number` | default: `0` (number of renderable items)
117117

118118
- **awaitMore**: `bool` | default: `false` (if true keeps the sentinel from detaching)
119119

docs/docs/components/AsyncList/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export default class extends React.Component {
5959
this.feedList(repos.filter(repo => repo.fork === false && repo.language));
6060
})
6161
.catch(err => {
62-
console.error(err); // eslint-disable-line
62+
console.error(err); // eslint-disable-line
6363
this.feedList([]);
6464
});
6565
};
@@ -89,7 +89,7 @@ export default class extends React.Component {
8989
<List
9090
awaitMore={this.state.awaitMore}
9191
itemsRenderer={this.renderItems}
92-
itemsLength={this.state.repos.length}
92+
currentLength={this.state.repos.length}
9393
onIntersection={this.handleLoadMore}
9494
pageSize={PAGE_SIZE}
9595
>

docs/docs/components/Axis/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ const itemsRenderer = (items, ref) => (
88
);
99

1010
// eslint-disable-next-line react/no-multi-comp
11-
export default () => <List axis="x" itemsLength={Infinity} itemsRenderer={itemsRenderer} pageSize={40} />;
11+
export default () => <List axis="x" currentLength={Infinity} itemsRenderer={itemsRenderer} pageSize={40} />;

docs/docs/components/InfiniteList/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ const itemsRenderer = (items, ref) => (
88
);
99

1010
// eslint-disable-next-line react/no-multi-comp
11-
export default () => <List itemsLength={Infinity} itemsRenderer={itemsRenderer} pageSize={40} />;
11+
export default () => <List currentLength={Infinity} itemsRenderer={itemsRenderer} pageSize={40} />;

docs/recipes/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
### Asynchonous Repo List
44

5-
When the sentinel comes into view, you can use the callback to load data, create the next items, and attach them. For this case we're loading Github repositories with pagination. We assume that we don't know the total `itemsLength` and we'll want to keep fetching until the (unknown) end of the list. The solution here is to pass the prop `awaitMore:bool = true`, so that the sentinel awaits for more items.
5+
When the sentinel comes into view, you can use the callback to load data, create the next items, and attach them. For this case we're loading Github repositories with pagination. We assume that we don't know the total length and we'll want to keep fetching until the (unknown) end of the list. The solution here is to pass the prop `awaitMore:bool = true`, so that the sentinel awaits for more items.
66

77
```jsx
88
import React from 'react';
@@ -67,7 +67,7 @@ export default class extends React.Component {
6767
<List
6868
awaitMore={this.state.awaitMore}
6969
itemsRenderer={this.renderItems}
70-
itemsLength={this.state.repos.length}
70+
currentLength={this.state.repos.length}
7171
onIntersection={this.handleLoadMore}
7272
pageSize={PAGE_SIZE}
7373
>
@@ -79,7 +79,7 @@ export default class extends React.Component {
7979
}
8080
```
8181

82-
If it's possible to get the total `itemsLength` in advance, we won't need `awaitMore` and the `pageSize` will be used to paginate results until we reach the bottom of the list.
82+
If the total amount of items are prefetched and available, we won't need `awaitMore` and the `pageSize` will be used to paginate results until we reach the bottom of the list.
8383

8484
### Infinite Synchronous List
8585

@@ -88,7 +88,7 @@ import React from 'react';
8888
import List from '@researchgate/react-intersection-list';
8989

9090
export default () => (
91-
<List itemsLength={Infinity}>
91+
<List currentLength={Infinity}>
9292
{(index, key) => <div key={key}>{index}</div>}
9393
</List>
9494
);

src/List.js

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default class List extends React.PureComponent {
1111
axis: PropTypes.oneOf(['x', 'y']),
1212
children: PropTypes.func,
1313
initialIndex: PropTypes.number,
14-
itemsLength: PropTypes.number,
14+
currentLength: PropTypes.number,
1515
itemsRenderer: PropTypes.func,
1616
onIntersection: PropTypes.func,
1717
pageSize: PropTypes.number,
@@ -22,7 +22,7 @@ export default class List extends React.PureComponent {
2222
axis: 'y',
2323
children: (index, key) => <div key={key}>{index}</div>,
2424
initialIndex: 0,
25-
itemsLength: 0,
25+
currentLength: 0,
2626
itemsRenderer: (items, ref) => <div ref={ref}>{items}</div>,
2727
pageSize: 10,
2828
threshold: '100px',
@@ -31,8 +31,16 @@ export default class List extends React.PureComponent {
3131
constructor(props) {
3232
super(props);
3333

34+
// eslint-disable-next-line no-undef
35+
if (process.env.NODE_ENV !== 'production') {
36+
warning(
37+
!props.hasOwnProperty('itemsLength'),
38+
'itemsLength is deprecated and will be removed in the next major version. Use currentLength instead.',
39+
);
40+
}
41+
3442
this.state = {
35-
size: this.computeSize(props.pageSize, props.itemsLength),
43+
size: this.computeSize(props.pageSize, props.currentLength),
3644
};
3745

3846
this.checkedForIntersection = this.state.size === 0;
@@ -46,20 +54,21 @@ export default class List extends React.PureComponent {
4654
};
4755

4856
handleUpdate = ({ isIntersecting }) => {
49-
const { pageSize, itemsLength, onIntersection, awaitMore } = this.props;
57+
const { pageSize, currentLength, onIntersection, awaitMore } = this.props;
5058
const { size } = this.state;
5159

5260
if (!this.checkedForIntersection) {
5361
this.checkedForIntersection = true;
5462
warning(
5563
!isIntersecting,
56-
'The sentinel detected a viewport with a bigger size than the size of its items. This could lead to detrimental behavior, e.g.: triggering more than one `onIntersection` callback at the start.\n' +
57-
'To prevent this, use either a bigger `pageSize` value or avoid using the prop `awaitMore` initially.',
64+
'the sentinel detected a viewport with a bigger size than the size of its items. ' +
65+
'This could lead to detrimental behavior, e.g.: triggering more than one onIntersection callback at the start.\n' +
66+
'To prevent this, use either a bigger `pageSize` value or avoid using the prop awaitMore initially.',
5867
);
5968
}
6069

6170
if (isIntersecting) {
62-
const nextSize = this.computeSize(size + pageSize, itemsLength);
71+
const nextSize = this.computeSize(size + pageSize, currentLength);
6372
this.setState({ size: nextSize });
6473

6574
if (onIntersection && (!awaitMore || this.awaitIntersection)) {
@@ -71,12 +80,12 @@ export default class List extends React.PureComponent {
7180
}
7281
};
7382

74-
computeSize(pageSize, itemsLength) {
75-
return Math.min(pageSize, itemsLength);
83+
computeSize(pageSize, currentLength) {
84+
return Math.min(pageSize, currentLength);
7685
}
7786

7887
renderItems() {
79-
const { children, itemsRenderer, initialIndex, itemsLength, threshold, axis, awaitMore } = this.props;
88+
const { children, itemsRenderer, initialIndex, currentLength, threshold, axis, awaitMore } = this.props;
8089
const { size } = this.state;
8190
const items = [];
8291

@@ -85,7 +94,7 @@ export default class List extends React.PureComponent {
8594
}
8695

8796
let sentinel;
88-
if (size < itemsLength || awaitMore) {
97+
if (size < currentLength || awaitMore) {
8998
sentinel = (
9099
<Sentinel
91100
key="sentinel"
@@ -109,9 +118,9 @@ export default class List extends React.PureComponent {
109118
});
110119
}
111120

112-
componentWillReceiveProps({ pageSize, itemsLength }) {
113-
if (this.props.pageSize !== pageSize || this.props.itemsLength !== itemsLength) {
114-
const nextSize = this.computeSize(this.state.size + pageSize, itemsLength);
121+
componentWillReceiveProps({ pageSize, currentLength }) {
122+
if (this.props.pageSize !== pageSize || this.props.currentLength !== currentLength) {
123+
const nextSize = this.computeSize(this.state.size + pageSize, currentLength);
115124
this.setState({ size: nextSize });
116125
}
117126
}

src/__tests__/List.spec.js

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,16 @@ test('renders without crashing', () => {
3333
createTree();
3434
});
3535

36+
test('warns about deprecated `itemsLength` prop', () => {
37+
expect(() => createTree({ itemsLength: 10 })).toThrowError(
38+
'Warning: itemsLength is deprecated and will be removed in the next major version. Use currentLength instead.',
39+
);
40+
});
41+
3642
describe('children', () => {
3743
test('receives correct arguments given default props', () => {
3844
const spy = jest.fn();
39-
createTree({ itemsLength: 10, children: spy });
45+
createTree({ currentLength: 10, children: spy });
4046
expect(spy).toHaveBeenCalledTimes(10);
4147
expect(spy).lastCalledWith(9, 9);
4248
});
@@ -45,7 +51,7 @@ describe('children', () => {
4551
const spy = jest.fn();
4652
createTree({
4753
initialIndex: 10,
48-
itemsLength: 30,
54+
currentLength: 30,
4955
pageSize: 15,
5056
children: spy,
5157
});
@@ -56,52 +62,52 @@ describe('children', () => {
5662

5763
describe('render items', () => {
5864
test('sentinel observes if available items in view', () => {
59-
const json = createTree({ itemsLength: 10, pageSize: 5 }).toJSON();
65+
const json = createTree({ currentLength: 10, pageSize: 5 }).toJSON();
6066
const children = json.children;
6167
expect(children.length).toBe(6);
6268
expect(children[children.length - 1].type).toBe('sentinel');
6369
});
6470

6571
test('sentinel gone if no items available in view', () => {
66-
const json = createTree({ itemsLength: 5, pageSize: 5 }).toJSON();
72+
const json = createTree({ currentLength: 5, pageSize: 5 }).toJSON();
6773
const children = json.children;
6874
expect(children.length).toBe(5);
6975
expect(children[children.length - 1].type).toBe('div');
7076
});
7177

72-
test('sentinel observes again if `itemsLength` is extended without re-rendering', () => {
73-
const tree = createTree({ itemsLength: 5, pageSize: 5 });
78+
test('sentinel observes again if `currentLength` is extended without re-rendering', () => {
79+
const tree = createTree({ currentLength: 5, pageSize: 5 });
7480
const spy = jest.spyOn(tree.getInstance(), 'render');
7581
expect(tree.toJSON().children.length).toBe(5);
76-
tree.update(<List pageSize={5} itemsLength={20} />);
82+
tree.update(<List pageSize={5} currentLength={20} />);
7783
expect(tree.toJSON().children.length).toBe(11);
7884
expect(spy).toHaveBeenCalledTimes(1);
7985
});
8086

8187
test('sentinel observes again if pageSize is extended without re-rendering', () => {
82-
const tree = createTree({ itemsLength: 100, pageSize: 10 });
88+
const tree = createTree({ currentLength: 100, pageSize: 10 });
8389
const spy = jest.spyOn(tree.getInstance(), 'render');
8490
expect(tree.toJSON().children.length).toBe(11);
85-
tree.update(<List itemsLength={100} pageSize={20} />);
91+
tree.update(<List currentLength={100} pageSize={20} />);
8692
expect(tree.toJSON().children.length).toBe(31);
8793
expect(spy).toHaveBeenCalledTimes(1);
8894
});
8995

90-
test('sentinel not present if `itemsLength` updates from zero', () => {
91-
const tree = createTree({ itemsLength: 0 });
96+
test('sentinel not present if `currentLength` updates from zero', () => {
97+
const tree = createTree({ currentLength: 0 });
9298
const children = tree.toJSON().children;
9399
expect(children).toBeNull();
94-
tree.update(<List itemsLength={8} />);
100+
tree.update(<List currentLength={8} />);
95101
const newChildren = tree.toJSON().children;
96102
expect(newChildren.length).toBe(8);
97103
expect(newChildren[newChildren.length - 1].type).toBe('div');
98104
});
99105

100-
test('sentinel observes with `awaitMore` bypassing the `itemsLength` check', () => {
101-
const tree = createTree({ itemsLength: 5 });
106+
test('sentinel observes with `awaitMore` bypassing the `currentLength` check', () => {
107+
const tree = createTree({ currentLength: 5 });
102108
const children = tree.toJSON().children;
103109
expect(children.length).toBe(5);
104-
tree.update(<List itemsLength={5} awaitMore />);
110+
tree.update(<List currentLength={5} awaitMore />);
105111
const newChildren = tree.toJSON().children;
106112
expect(newChildren.length).toBe(6);
107113
expect(newChildren[newChildren.length - 1].type).toBe('sentinel');
@@ -110,52 +116,52 @@ describe('render items', () => {
110116

111117
describe('setRootNode', () => {
112118
test('ref callback sets root node', () => {
113-
createTree({ itemsLength: 20 });
119+
createTree({ currentLength: 20 });
114120
expect(mockCallback).toBeCalledWith(target);
115121
});
116122

117123
test('ref callback does sets root node if unmounting', () => {
118-
renderer.create(<List itemsLength={20} />, {
124+
renderer.create(<List currentLength={20} />, {
119125
createNodeMock: () => undefined,
120126
});
121127
expect(mockCallback).not.toBeCalled();
122128
});
123129

124130
test('ref callback does sets root node without sentinel', () => {
125-
const tree = createTree({ itemsLength: 10 });
131+
const tree = createTree({ currentLength: 10 });
126132
expect(tree.getInstance().setRootNode).toBeUndefined();
127133
});
128134

129135
test('ref callback sets a null root if it does not have overflow', () => {
130136
window.getComputedStyle = jest.fn(() => ({
131137
overflowY: 'visible',
132138
}));
133-
createTree({ itemsLength: 20 });
139+
createTree({ currentLength: 20 });
134140
expect(mockCallback).toBeCalledWith(null);
135141
});
136142
});
137143

138144
describe('handleUpdate', () => {
139145
test('throws once if sentinel intersects items on mount', () => {
140-
const instance = createTree({ itemsLength: 10 }).getInstance();
146+
const instance = createTree({ currentLength: 10 }).getInstance();
141147
expect(() => instance.handleUpdate({ isIntersecting: true })).toThrowErrorMatchingSnapshot();
142148
expect(() => instance.handleUpdate({ isIntersecting: true })).not.toThrow();
143149
expect(() =>
144-
createTree({ itemsLength: 0 })
150+
createTree({ currentLength: 0 })
145151
.getInstance()
146152
.handleUpdate({ isIntersecting: true }),
147153
).not.toThrow();
148154
});
149155

150156
test('sets next size value computed into `pageSize`', () => {
151-
const instance = createTree({ itemsLength: 20 }).getInstance();
157+
const instance = createTree({ currentLength: 20 }).getInstance();
152158
instance.handleUpdate({ isIntersecting: false });
153159
instance.handleUpdate({ isIntersecting: true });
154160
expect(instance.state.size).toBe(20);
155161
});
156162

157-
test('sets next size value computed into `itemsLength`', () => {
158-
const instance = createTree({ itemsLength: 15 }).getInstance();
163+
test('sets next size value computed into `currentLength`', () => {
164+
const instance = createTree({ currentLength: 15 }).getInstance();
159165
instance.handleUpdate({ isIntersecting: false });
160166
instance.handleUpdate({ isIntersecting: true });
161167
expect(instance.state.size).toBe(15);
@@ -164,7 +170,7 @@ describe('handleUpdate', () => {
164170
test('calls `onIntersection` each time when `awaitIntersection` is falsy', () => {
165171
const spy = jest.fn();
166172
const instance = createTree({
167-
itemsLength: 30,
173+
currentLength: 30,
168174
onIntersection: spy,
169175
}).getInstance();
170176
instance.handleUpdate({ isIntersecting: false });
@@ -178,14 +184,14 @@ describe('handleUpdate', () => {
178184
const spy = jest.fn();
179185
const tree = createTree({
180186
awaitMore: true,
181-
itemsLength: 10,
187+
currentLength: 10,
182188
onIntersection: spy,
183189
});
184190
tree.getInstance().handleUpdate({ isIntersecting: false });
185191
tree.getInstance().handleUpdate({ isIntersecting: true });
186192
tree.getInstance().handleUpdate({ isIntersecting: true });
187193
expect(tree.getInstance().state.size).toBe(10);
188-
tree.update(<List awaitMore itemsLength={30} onIntersection={spy} />);
194+
tree.update(<List awaitMore currentLength={30} onIntersection={spy} />);
189195
tree.getInstance().handleUpdate({ isIntersecting: true });
190196
expect(spy).toHaveBeenCalledTimes(2);
191197
});
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`handleUpdate throws once if sentinel intersects items on mount 1`] = `
4-
"Warning: The sentinel detected a viewport with a bigger size than the size of its items. This could lead to detrimental behavior, e.g.: triggering more than one \`onIntersection\` callback at the start.
5-
To prevent this, use either a bigger \`pageSize\` value or avoid using the prop \`awaitMore\` initially."
4+
"Warning: the sentinel detected a viewport with a bigger size than the size of its items. This could lead to detrimental behavior, e.g.: triggering more than one onIntersection callback at the start.
5+
To prevent this, use either a bigger \`pageSize\` value or avoid using the prop awaitMore initially."
66
`;

0 commit comments

Comments
 (0)