Skip to content

Commit 8a82503

Browse files
vonovakfacebook-github-bot
authored andcommitted
fix SectionList scrollToLocation and prevent regressions (#25997)
Summary: Recently there were quite a few changes to this functionality, and they caused breakages #21577 #24034 #24734 #24735 Currently, whichever `viewOffset` I pass, it will be overridden (either by 0 or something computed in the if body). This fixes the issue and also adds tests to make sure there is no regression. ## Changelog [Javascript] [Fixed] - VirtualizedSectionList scrollToLocation viewOffset param ignored Pull Request resolved: #25997 Test Plan: tests pass Differential Revision: D16784036 Pulled By: cpojer fbshipit-source-id: 46421250993785176634b30a2629a6e12f0c2278
1 parent 427b54e commit 8a82503

File tree

3 files changed

+107
-15
lines changed

3 files changed

+107
-15
lines changed

Libraries/Lists/SectionList.js

+2-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type {ViewToken} from './ViewabilityHelper';
1818
import type {
1919
SectionBase as _SectionBase,
2020
Props as VirtualizedSectionListProps,
21+
ScrollToLocationParamsType,
2122
} from './VirtualizedSectionList';
2223

2324
type Item = any;
@@ -245,13 +246,7 @@ class SectionList<SectionT: SectionBase<any>> extends React.PureComponent<
245246
* Note: cannot scroll to locations outside the render window without specifying the
246247
* `getItemLayout` prop.
247248
*/
248-
scrollToLocation(params: {
249-
animated?: ?boolean,
250-
itemIndex: number,
251-
sectionIndex: number,
252-
viewOffset?: number,
253-
viewPosition?: number,
254-
}) {
249+
scrollToLocation(params: ScrollToLocationParamsType) {
255250
if (this._wrapperListRef != null) {
256251
this._wrapperListRef.scrollToLocation(params);
257252
}

Libraries/Lists/VirtualizedSectionList.js

+11-8
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,19 @@ type OptionalProps<SectionT: SectionBase<any>> = {
119119
export type Props<SectionT> = RequiredProps<SectionT> &
120120
OptionalProps<SectionT> &
121121
VirtualizedListProps;
122+
export type ScrollToLocationParamsType = {|
123+
animated?: ?boolean,
124+
itemIndex: number,
125+
sectionIndex: number,
126+
viewOffset?: number,
127+
viewPosition?: number,
128+
|};
122129

123130
type DefaultProps = {|
124131
...typeof VirtualizedList.defaultProps,
125132
data: $ReadOnlyArray<Item>,
126133
|};
134+
127135
type State = {childProps: VirtualizedListProps};
128136

129137
/**
@@ -139,22 +147,17 @@ class VirtualizedSectionList<
139147
data: [],
140148
};
141149

142-
scrollToLocation(params: {
143-
animated?: ?boolean,
144-
itemIndex: number,
145-
sectionIndex: number,
146-
viewPosition?: number,
147-
}) {
150+
scrollToLocation(params: ScrollToLocationParamsType) {
148151
let index = params.itemIndex;
149152
for (let i = 0; i < params.sectionIndex; i++) {
150153
index += this.props.getItemCount(this.props.sections[i].data) + 2;
151154
}
152-
let viewOffset = 0;
155+
let viewOffset = params.viewOffset || 0;
153156
if (params.itemIndex > 0 && this.props.stickySectionHeadersEnabled) {
154157
const frame = this._listRef._getFrameMetricsApprox(
155158
index - params.itemIndex,
156159
);
157-
viewOffset = frame.length;
160+
viewOffset += frame.length;
158161
}
159162
const toIndexParams = {
160163
...params,

Libraries/Lists/__tests__/VirtualizedSectionList-test.js

+94
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,98 @@ describe('VirtualizedSectionList', () => {
161161
);
162162
expect(component).toMatchSnapshot();
163163
});
164+
165+
describe('scrollToLocation', () => {
166+
const ITEM_HEIGHT = 100;
167+
168+
const createVirtualizedSectionList = props => {
169+
const component = ReactTestRenderer.create(
170+
<VirtualizedSectionList
171+
sections={[
172+
{title: 's1', data: [{key: 'i1.1'}, {key: 'i1.2'}, {key: 'i1.3'}]},
173+
{title: 's2', data: [{key: 'i2.1'}, {key: 'i2.2'}, {key: 'i2.3'}]},
174+
]}
175+
renderItem={({item}) => <item value={item.key} />}
176+
getItem={(data, key) => data[key]}
177+
getItemCount={data => data.length}
178+
getItemLayout={(data, index) => ({
179+
length: ITEM_HEIGHT,
180+
offset: ITEM_HEIGHT * index,
181+
index,
182+
})}
183+
{...props}
184+
/>,
185+
);
186+
const instance = component.getInstance();
187+
const spy = jest.fn();
188+
instance._listRef.scrollToIndex = spy;
189+
return {
190+
instance,
191+
spy,
192+
};
193+
};
194+
195+
it('when sticky stickySectionHeadersEnabled={true}, header height is added to the developer-provided viewOffset', () => {
196+
const {instance, spy} = createVirtualizedSectionList({
197+
stickySectionHeadersEnabled: true,
198+
});
199+
200+
const viewOffset = 25;
201+
202+
instance.scrollToLocation({
203+
sectionIndex: 0,
204+
itemIndex: 1,
205+
viewOffset,
206+
});
207+
expect(spy).toHaveBeenCalledWith({
208+
index: 1,
209+
itemIndex: 1,
210+
sectionIndex: 0,
211+
viewOffset: viewOffset + ITEM_HEIGHT,
212+
});
213+
});
214+
215+
it.each([
216+
[
217+
// prevents #18098
218+
{sectionIndex: 0, itemIndex: 0},
219+
{
220+
index: 0,
221+
itemIndex: 0,
222+
sectionIndex: 0,
223+
viewOffset: 0,
224+
},
225+
],
226+
[
227+
{sectionIndex: 2, itemIndex: 1},
228+
{
229+
index: 11,
230+
itemIndex: 1,
231+
sectionIndex: 2,
232+
viewOffset: 0,
233+
},
234+
],
235+
[
236+
{
237+
sectionIndex: 0,
238+
itemIndex: 1,
239+
viewOffset: 25,
240+
},
241+
{
242+
index: 1,
243+
itemIndex: 1,
244+
sectionIndex: 0,
245+
viewOffset: 25,
246+
},
247+
],
248+
])(
249+
'given sectionIndex, itemIndex and viewOffset, scrollToIndex is called with correct params',
250+
(scrollToLocationParams, expected) => {
251+
const {instance, spy} = createVirtualizedSectionList();
252+
253+
instance.scrollToLocation(scrollToLocationParams);
254+
expect(spy).toHaveBeenCalledWith(expected);
255+
},
256+
);
257+
});
164258
});

0 commit comments

Comments
 (0)