Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix SectionList scrollToLocation and prevent regressions #25997

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 2 additions & 7 deletions Libraries/Lists/SectionList.js
Expand Up @@ -18,6 +18,7 @@ import type {ViewToken} from './ViewabilityHelper';
import type {
SectionBase as _SectionBase,
Props as VirtualizedSectionListProps,
ScrollToLocationParamsType,
} from './VirtualizedSectionList';

type Item = any;
Expand Down Expand Up @@ -245,13 +246,7 @@ class SectionList<SectionT: SectionBase<any>> extends React.PureComponent<
* Note: cannot scroll to locations outside the render window without specifying the
* `getItemLayout` prop.
*/
scrollToLocation(params: {
animated?: ?boolean,
itemIndex: number,
sectionIndex: number,
viewOffset?: number,
viewPosition?: number,
}) {
scrollToLocation(params: ScrollToLocationParamsType) {
if (this._wrapperListRef != null) {
this._wrapperListRef.scrollToLocation(params);
}
Expand Down
19 changes: 11 additions & 8 deletions Libraries/Lists/VirtualizedSectionList.js
Expand Up @@ -119,11 +119,19 @@ type OptionalProps<SectionT: SectionBase<any>> = {
export type Props<SectionT> = RequiredProps<SectionT> &
OptionalProps<SectionT> &
VirtualizedListProps;
export type ScrollToLocationParamsType = {|
animated?: ?boolean,
itemIndex: number,
sectionIndex: number,
viewOffset?: number,
viewPosition?: number,
|};

type DefaultProps = {|
...typeof VirtualizedList.defaultProps,
data: $ReadOnlyArray<Item>,
|};

type State = {childProps: VirtualizedListProps};

/**
Expand All @@ -139,22 +147,17 @@ class VirtualizedSectionList<
data: [],
};

scrollToLocation(params: {
animated?: ?boolean,
itemIndex: number,
sectionIndex: number,
viewPosition?: number,
}) {
scrollToLocation(params: ScrollToLocationParamsType) {
let index = params.itemIndex;
for (let i = 0; i < params.sectionIndex; i++) {
index += this.props.getItemCount(this.props.sections[i].data) + 2;
}
let viewOffset = 0;
let viewOffset = params.viewOffset || 0;
if (params.itemIndex > 0 && this.props.stickySectionHeadersEnabled) {
const frame = this._listRef._getFrameMetricsApprox(
index - params.itemIndex,
);
viewOffset = frame.length;
viewOffset += frame.length;
}
const toIndexParams = {
...params,
Expand Down
94 changes: 94 additions & 0 deletions Libraries/Lists/__tests__/VirtualizedSectionList-test.js
Expand Up @@ -161,4 +161,98 @@ describe('VirtualizedSectionList', () => {
);
expect(component).toMatchSnapshot();
});

describe('scrollToLocation', () => {
const ITEM_HEIGHT = 100;

const createVirtualizedSectionList = props => {
const component = ReactTestRenderer.create(
<VirtualizedSectionList
sections={[
{title: 's1', data: [{key: 'i1.1'}, {key: 'i1.2'}, {key: 'i1.3'}]},
{title: 's2', data: [{key: 'i2.1'}, {key: 'i2.2'}, {key: 'i2.3'}]},
]}
renderItem={({item}) => <item value={item.key} />}
getItem={(data, key) => data[key]}
getItemCount={data => data.length}
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
{...props}
/>,
);
const instance = component.getInstance();
const spy = jest.fn();
instance._listRef.scrollToIndex = spy;
return {
instance,
spy,
};
};

it('when sticky stickySectionHeadersEnabled={true}, header height is added to the developer-provided viewOffset', () => {
const {instance, spy} = createVirtualizedSectionList({
stickySectionHeadersEnabled: true,
});

const viewOffset = 25;

instance.scrollToLocation({
sectionIndex: 0,
itemIndex: 1,
viewOffset,
});
expect(spy).toHaveBeenCalledWith({
index: 1,
itemIndex: 1,
sectionIndex: 0,
viewOffset: viewOffset + ITEM_HEIGHT,
});
});

it.each([
[
// prevents #18098
{sectionIndex: 0, itemIndex: 0},
{
index: 0,
itemIndex: 0,
sectionIndex: 0,
viewOffset: 0,
},
],
[
{sectionIndex: 2, itemIndex: 1},
{
index: 11,
itemIndex: 1,
sectionIndex: 2,
viewOffset: 0,
},
],
[
{
sectionIndex: 0,
itemIndex: 1,
viewOffset: 25,
},
{
index: 1,
itemIndex: 1,
sectionIndex: 0,
viewOffset: 25,
},
],
])(
'given sectionIndex, itemIndex and viewOffset, scrollToIndex is called with correct params',
(scrollToLocationParams, expected) => {
const {instance, spy} = createVirtualizedSectionList();

instance.scrollToLocation(scrollToLocationParams);
expect(spy).toHaveBeenCalledWith(expected);
},
);
});
});