Skip to content

Commit c5c1798

Browse files
Antoine Doubovetzkyfacebook-github-bot
Antoine Doubovetzky
authored andcommitted
Fix VirtualizedList with initialScrollIndex not rendering all elements when data is updated (#33558)
Summary: Fixes #33529 (note that I reproduced the bug on iOS too). The bug comes from the fact that we were using `this._scrollMetrics.offset` to determine if the initial scroll was done. But sometimes it equals 0 even after the initial scroll is done, for example when the content does not fill the list. So I replaced it with `this._hasDoneInitialScroll`. I believe that `this._hasDoneInitialScroll` was not used in the first place because it was introduced later (3 years ago vs 5 years ago for the original code). The replacement correctly fixes the broken test case and the example given in the issue. Then I had to update two test cases (rename the first and remove the second), that shows explicitly the broken behavior: we have to simulate the initial scroll for the content to be adjusted, so when the content does not fill the view and the scroll cannot be executed, the content is not adjusted. ## Changelog [General] [Fix] - Fix VirtualizedList with initialScrollIndex not rendering all elements when data is updated Pull Request resolved: #33558 Test Plan: - I added a broken test case based on the issue - I tested with the RNTesterApp using the code example given in the issue Reviewed By: ryancat Differential Revision: D35503114 Pulled By: yungsters fbshipit-source-id: 67bb75d7cf1ebac0d59127d0d45afbaa3167dcf3
1 parent 2c52131 commit c5c1798

File tree

3 files changed

+96
-150
lines changed

3 files changed

+96
-150
lines changed

Libraries/Lists/VirtualizedList.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1754,7 +1754,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
17541754
// we'll wipe out the initialNumToRender rendered elements starting at initialScrollIndex.
17551755
// So let's wait until we've scrolled the view to the right place. And until then,
17561756
// we will trust the initialScrollIndex suggestion.
1757-
if (!this.props.initialScrollIndex || this._scrollMetrics.offset) {
1757+
if (!this.props.initialScrollIndex || this._hasDoneInitialScroll) {
17581758
newState = computeWindowedRenderLimits(
17591759
this.props.data,
17601760
this.props.getItemCount,

Libraries/Lists/__tests__/VirtualizedList-test.js

+25-10
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,7 @@ it('does not adjust render area until content area layed out', () => {
862862
expect(component).toMatchSnapshot();
863863
});
864864

865-
it('does not adjust render area with non-zero initialScrollIndex until scrolled', () => {
865+
it('adjusts render area with non-zero initialScrollIndex', () => {
866866
const items = generateItems(20);
867867
const ITEM_HEIGHT = 10;
868868

@@ -885,18 +885,17 @@ it('does not adjust render area with non-zero initialScrollIndex until scrolled'
885885
viewport: {width: 10, height: 50},
886886
content: {width: 10, height: 200},
887887
});
888+
888889
performAllBatches();
889890
});
890891

891-
// Layout information from before the time we scroll to initial index may not
892-
// correspond to the area "initialScrollIndex" points to. Expect only the 5
893-
// initial items (starting at initialScrollIndex) to be rendered after
894-
// processing all batch work, even though the windowSize allows for more.
892+
// We should expand the render area after receiving a message indcating we
893+
// arrived at initialScrollIndex.
895894
expect(component).toMatchSnapshot();
896895
});
897896

898-
it('adjusts render area with non-zero initialScrollIndex after scrolled', () => {
899-
const items = generateItems(20);
897+
it('renders new items when data is updated with non-zero initialScrollIndex', () => {
898+
const items = generateItems(2);
900899
const ITEM_HEIGHT = 10;
901900

902901
let component;
@@ -918,13 +917,29 @@ it('adjusts render area with non-zero initialScrollIndex after scrolled', () =>
918917
viewport: {width: 10, height: 50},
919918
content: {width: 10, height: 200},
920919
});
920+
performAllBatches();
921+
});
922+
923+
const newItems = generateItems(4);
921924

922-
simulateScroll(component, {x: 0, y: 10});
925+
ReactTestRenderer.act(() => {
926+
component.update(
927+
<VirtualizedList
928+
initialNumToRender={5}
929+
initialScrollIndex={1}
930+
windowSize={10}
931+
maxToRenderPerBatch={10}
932+
{...baseItemProps(newItems)}
933+
{...fixedHeightItemLayoutProps(ITEM_HEIGHT)}
934+
/>,
935+
);
936+
});
937+
938+
ReactTestRenderer.act(() => {
923939
performAllBatches();
924940
});
925941

926-
// We should expand the render area after receiving a message indcating we
927-
// arrived at initialScrollIndex.
942+
// We expect all the items to be rendered
928943
expect(component).toMatchSnapshot();
929944
});
930945

Libraries/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap

+70-139
Original file line numberDiff line numberDiff line change
@@ -1600,7 +1600,7 @@ exports[`VirtualizedList warns if both renderItem or ListItemComponent are speci
16001600
</RCTScrollView>
16011601
`;
16021602

1603-
exports[`adjusts render area with non-zero initialScrollIndex after scrolled 1`] = `
1603+
exports[`adjusts render area with non-zero initialScrollIndex 1`] = `
16041604
<RCTScrollView
16051605
data={
16061606
Array [
@@ -2207,144 +2207,6 @@ exports[`does not adjust render area until content area layed out 1`] = `
22072207
</RCTScrollView>
22082208
`;
22092209

2210-
exports[`does not adjust render area with non-zero initialScrollIndex until scrolled 1`] = `
2211-
<RCTScrollView
2212-
data={
2213-
Array [
2214-
Object {
2215-
"key": 0,
2216-
},
2217-
Object {
2218-
"key": 1,
2219-
},
2220-
Object {
2221-
"key": 2,
2222-
},
2223-
Object {
2224-
"key": 3,
2225-
},
2226-
Object {
2227-
"key": 4,
2228-
},
2229-
Object {
2230-
"key": 5,
2231-
},
2232-
Object {
2233-
"key": 6,
2234-
},
2235-
Object {
2236-
"key": 7,
2237-
},
2238-
Object {
2239-
"key": 8,
2240-
},
2241-
Object {
2242-
"key": 9,
2243-
},
2244-
Object {
2245-
"key": 10,
2246-
},
2247-
Object {
2248-
"key": 11,
2249-
},
2250-
Object {
2251-
"key": 12,
2252-
},
2253-
Object {
2254-
"key": 13,
2255-
},
2256-
Object {
2257-
"key": 14,
2258-
},
2259-
Object {
2260-
"key": 15,
2261-
},
2262-
Object {
2263-
"key": 16,
2264-
},
2265-
Object {
2266-
"key": 17,
2267-
},
2268-
Object {
2269-
"key": 18,
2270-
},
2271-
Object {
2272-
"key": 19,
2273-
},
2274-
]
2275-
}
2276-
getItem={[Function]}
2277-
getItemCount={[Function]}
2278-
getItemLayout={[Function]}
2279-
initialNumToRender={5}
2280-
initialScrollIndex={1}
2281-
maxToRenderPerBatch={10}
2282-
onContentSizeChange={[Function]}
2283-
onLayout={[Function]}
2284-
onMomentumScrollBegin={[Function]}
2285-
onMomentumScrollEnd={[Function]}
2286-
onScroll={[Function]}
2287-
onScrollBeginDrag={[Function]}
2288-
onScrollEndDrag={[Function]}
2289-
renderItem={[Function]}
2290-
scrollEventThrottle={50}
2291-
stickyHeaderIndices={Array []}
2292-
windowSize={10}
2293-
>
2294-
<View>
2295-
<View
2296-
style={
2297-
Object {
2298-
"height": 10,
2299-
}
2300-
}
2301-
/>
2302-
<View
2303-
style={null}
2304-
>
2305-
<MockCellItem
2306-
value={1}
2307-
/>
2308-
</View>
2309-
<View
2310-
style={null}
2311-
>
2312-
<MockCellItem
2313-
value={2}
2314-
/>
2315-
</View>
2316-
<View
2317-
style={null}
2318-
>
2319-
<MockCellItem
2320-
value={3}
2321-
/>
2322-
</View>
2323-
<View
2324-
style={null}
2325-
>
2326-
<MockCellItem
2327-
value={4}
2328-
/>
2329-
</View>
2330-
<View
2331-
style={null}
2332-
>
2333-
<MockCellItem
2334-
value={5}
2335-
/>
2336-
</View>
2337-
<View
2338-
style={
2339-
Object {
2340-
"height": 140,
2341-
}
2342-
}
2343-
/>
2344-
</View>
2345-
</RCTScrollView>
2346-
`;
2347-
23482210
exports[`does not over-render when there is less than initialNumToRender cells 1`] = `
23492211
<RCTScrollView
23502212
data={
@@ -3250,6 +3112,75 @@ exports[`renders items before initialScrollIndex on first batch tick when virtua
32503112
</RCTScrollView>
32513113
`;
32523114

3115+
exports[`renders new items when data is updated with non-zero initialScrollIndex 1`] = `
3116+
<RCTScrollView
3117+
data={
3118+
Array [
3119+
Object {
3120+
"key": 0,
3121+
},
3122+
Object {
3123+
"key": 1,
3124+
},
3125+
Object {
3126+
"key": 2,
3127+
},
3128+
Object {
3129+
"key": 3,
3130+
},
3131+
]
3132+
}
3133+
getItem={[Function]}
3134+
getItemCount={[Function]}
3135+
getItemLayout={[Function]}
3136+
initialNumToRender={5}
3137+
initialScrollIndex={1}
3138+
maxToRenderPerBatch={10}
3139+
onContentSizeChange={[Function]}
3140+
onLayout={[Function]}
3141+
onMomentumScrollBegin={[Function]}
3142+
onMomentumScrollEnd={[Function]}
3143+
onScroll={[Function]}
3144+
onScrollBeginDrag={[Function]}
3145+
onScrollEndDrag={[Function]}
3146+
renderItem={[Function]}
3147+
scrollEventThrottle={50}
3148+
stickyHeaderIndices={Array []}
3149+
windowSize={10}
3150+
>
3151+
<View>
3152+
<View
3153+
style={null}
3154+
>
3155+
<MockCellItem
3156+
value={0}
3157+
/>
3158+
</View>
3159+
<View
3160+
style={null}
3161+
>
3162+
<MockCellItem
3163+
value={1}
3164+
/>
3165+
</View>
3166+
<View
3167+
style={null}
3168+
>
3169+
<MockCellItem
3170+
value={2}
3171+
/>
3172+
</View>
3173+
<View
3174+
style={null}
3175+
>
3176+
<MockCellItem
3177+
value={3}
3178+
/>
3179+
</View>
3180+
</View>
3181+
</RCTScrollView>
3182+
`;
3183+
32533184
exports[`renders no spacers up to initialScrollIndex on first render when virtualization disabled 1`] = `
32543185
<RCTScrollView
32553186
data={

0 commit comments

Comments
 (0)