diff --git a/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js b/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js
index d37d0d4a9154..19990d64c08a 100644
--- a/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js
+++ b/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js
@@ -12,27 +12,111 @@ import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import RNTesterText from '../../components/RNTesterText';
import * as React from 'react';
-import {StyleSheet, View} from 'react-native';
+import {useState} from 'react';
+import {
+ Button,
+ SectionList,
+ StyleSheet,
+ Text,
+ View,
+} from 'react-native';
+
+const INITIAL_SECTIONS = [
+ {title: 'Section', data: [{key: 'a'}, {key: 'b'}, {key: 'c'}]},
+];
+const REORDERED_SECTIONS = [
+ {title: 'Section', data: [{key: 'b'}, {key: 'a'}, {key: 'c'}]},
+];
+
+function ItemSeparatorReproducer({leadingItem, trailingItem}: any) {
+ const leading = leadingItem?.key ?? 'undefined';
+ const trailing = trailingItem?.key ?? 'undefined';
+ return (
+
+
+ leading: {leading} | trailing: {trailing}
+
+
+ );
+}
+
+function ItemSeparatorComponentBugPlayground() {
+ const [sections, setSections] = useState(INITIAL_SECTIONS);
+ const [reordered, setReordered] = useState(false);
-function Playground() {
return (
- Edit "RNTesterPlayground.js" to change this file
+ Bug: ItemSeparatorComponent receives stale leadingItem/trailingItem after
+ reorder. Tap "Reorder" to swap first two items. The separator between
+ "b" and "a" should show "leading: b | trailing: a". If it shows
+ "leading: a | trailing: c" or "undefined", the bug is present.
+
+ );
+}
+
+function Playground() {
+ return (
+
+
);
}
const styles = StyleSheet.create({
container: {
+ flex: 1,
padding: 10,
},
+ item: {
+ padding: 12,
+ backgroundColor: '#f0f0f0',
+ },
+ list: {
+ flex: 1,
+ },
+ sectionHeader: {
+ fontSize: 16,
+ fontWeight: 'bold',
+ paddingVertical: 8,
+ },
+ separator: {
+ height: 24,
+ backgroundColor: '#e0e0ff',
+ justifyContent: 'center',
+ paddingHorizontal: 8,
+ },
+ separatorText: {
+ fontSize: 12,
+ },
});
export default ({
title: 'Playground',
name: 'playground',
- description: 'Test out new features and ideas.',
+ description:
+ 'Test out new features and ideas. Includes reproducer for ItemSeparatorComponent leadingItem/trailingItem state bug.',
render: (): React.Node => ,
}: RNTesterModuleExample);
diff --git a/packages/virtualized-lists/Lists/__tests__/VirtualizedSectionList-test.js b/packages/virtualized-lists/Lists/__tests__/VirtualizedSectionList-test.js
index cc6b1da8d266..068c4560ef09 100644
--- a/packages/virtualized-lists/Lists/__tests__/VirtualizedSectionList-test.js
+++ b/packages/virtualized-lists/Lists/__tests__/VirtualizedSectionList-test.js
@@ -202,6 +202,64 @@ describe('VirtualizedSectionList', () => {
expect(component).toMatchSnapshot();
});
+ it('syncs ItemWithSeparator separator props when list re-renders with new leadingItem/trailingItem', async () => {
+ // Reproduces: When ItemWithSeparator re-renders with new props (e.g. after reorder),
+ // leadingItem/trailingItem can become stale (e.g. undefined when the row was previously at a boundary).
+ const separatorPropsReceived = [];
+ const ItemSeparatorWithCapture = props => {
+ separatorPropsReceived.push({
+ leadingItem: props.leadingItem?.key,
+ trailingItem: props.trailingItem?.key,
+ });
+ return ;
+ };
+
+ let setSections;
+ const initialSections = [
+ {title: 's0', data: [{key: 'a'}, {key: 'b'}, {key: 'c'}]},
+ ];
+ const reorderedSections = [
+ {title: 's0', data: [{key: 'b'}, {key: 'a'}, {key: 'c'}]},
+ ];
+
+ function ListWithState() {
+ const [sections, setSectionsState] = React.useState(initialSections);
+ setSections = setSectionsState;
+ return (
+ }
+ getItem={(data, index) => data[index]}
+ getItemCount={data => data.length}
+ keyExtractor={(item, index) => item.key}
+ />
+ );
+ }
+
+ let component;
+ await ReactTestRenderer.act(() => {
+ component = ReactTestRenderer.create();
+ });
+
+ separatorPropsReceived.length = 0;
+
+ await ReactTestRenderer.act(() => {
+ setSections(reorderedSections);
+ });
+
+ // After reorder: row "b" moved from index 1 to 0. Its trailing separator
+ // (between b and a) should show leadingItem: b, trailingItem: a.
+ // But buggy ItemWithSeparator keeps the initial
+ // state from when "b" was at index 1 (leadingItem: a, trailingItem: c), so
+ // the separator receives stale props and this assertion fails.
+ expect(separatorPropsReceived).toEqual(
+ expect.arrayContaining([
+ {leadingItem: 'b', trailingItem: 'a'},
+ ]),
+ );
+ });
+
describe('scrollToLocation', () => {
const ITEM_HEIGHT = 100;