diff --git a/packages/rn-tester/js/examples/Performance/ItemList.js b/packages/rn-tester/js/examples/Performance/ItemList.js
new file mode 100644
index 00000000000000..622b013906d572
--- /dev/null
+++ b/packages/rn-tester/js/examples/Performance/ItemList.js
@@ -0,0 +1,91 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow strict-local
+ * @format
+ * @oncall react_native
+ */
+
+'use strict';
+
+import type {ScrollEvent} from 'react-native/Libraries/Types/CoreEventTypes';
+import type {ItemDataType} from './itemData';
+
+import * as React from 'react';
+import {StyleSheet, View, Text, FlatList, ScrollView} from 'react-native';
+
+function Item(props: {data: ItemDataType}): React.Node {
+ const {data} = props;
+ return (
+
+ {data.name}
+ {`Age: ${data.age}`}
+ {`Address: ${data.address}`}
+ {`id: ${data.id}`}
+
+ );
+}
+
+interface ItemListProps {
+ data: ItemDataType[];
+ useFlatList?: boolean;
+ onScroll?: (evt: ScrollEvent) => void;
+}
+
+function renderItem({item}: {item: ItemDataType, ...}): React.MixedElement {
+ return ;
+}
+
+function ItemList(props: ItemListProps): React.Node {
+ const {data, useFlatList = false, onScroll} = props;
+
+ return (
+
+ {useFlatList ? (
+
+ ) : (
+
+ {data.map(item => (
+
+ ))}
+
+ )}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'column',
+ alignItems: 'flex-start',
+ justifyContent: 'flex-start',
+ padding: 5,
+ },
+ itemContainer: {
+ width: 200,
+ flexDirection: 'column',
+ alignItems: 'flex-start',
+ justifyContent: 'flex-start',
+ padding: 5,
+ backgroundColor: 'gray',
+ marginHorizontal: 5,
+ },
+ itemName: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ marginBottom: 5,
+ },
+ itemDescription: {
+ fontSize: 15,
+ },
+});
+
+export default ItemList;
diff --git a/packages/rn-tester/js/examples/Performance/PerformanceComparisonExample.js b/packages/rn-tester/js/examples/Performance/PerformanceComparisonExample.js
new file mode 100644
index 00000000000000..6b71ccf6371efc
--- /dev/null
+++ b/packages/rn-tester/js/examples/Performance/PerformanceComparisonExample.js
@@ -0,0 +1,142 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @format
+ * @flow strict-local
+ * @oncall react_native
+ */
+
+'use strict';
+
+import * as React from 'react';
+import {StyleSheet, View, Text} from 'react-native';
+import RNTesterPage from '../../components/RNTesterPage';
+import RNTesterButton from '../../components/RNTesterButton';
+import ReRenderWithObjectPropExample from './ReRenderWithObjectPropExample';
+import ReRenderWithNonPureChildExample from './ReRenderWithNonPureChildExample';
+
+const {useState, useCallback, useMemo} = React;
+const SHOW_NOTHING = 'SHOW_NOTHING';
+const SHOW_GOOD_EXAMPLE = 'SHOW_GOOD_EXAMPLE';
+const SHOW_BAD_EXAMPLE = 'SHOW_BAD_EXAMPLE';
+
+function PerfExampleWrapper(props: {
+ badExample: React.Node,
+ goodExample: React.Node,
+ badExampleScript?: string,
+ goodExampleScript?: string,
+}): React.Node {
+ const {badExample, goodExample, badExampleScript, goodExampleScript} = props;
+ const [loadExample, setLoadExample] = useState(SHOW_NOTHING);
+ const toggleGoodExample = useCallback(
+ () =>
+ setLoadExample(
+ loadExample === SHOW_GOOD_EXAMPLE ? SHOW_NOTHING : SHOW_GOOD_EXAMPLE,
+ ),
+ [setLoadExample, loadExample],
+ );
+ const toggleBadExample = useCallback(
+ () =>
+ setLoadExample(
+ loadExample === SHOW_BAD_EXAMPLE ? SHOW_NOTHING : SHOW_BAD_EXAMPLE,
+ ),
+ [setLoadExample, loadExample],
+ );
+
+ const badExampleContents = useMemo(() => {
+ const result: React.Node[] = [];
+ if (loadExample === SHOW_BAD_EXAMPLE) {
+ if (badExampleScript != null) {
+ result.push({badExampleScript});
+ }
+ result.push({badExample});
+ }
+ return result;
+ }, [loadExample, badExample, badExampleScript]);
+
+ const goodExampleContents = useMemo(() => {
+ const result: React.Node[] = [];
+ if (loadExample === SHOW_GOOD_EXAMPLE) {
+ if (goodExampleScript != null) {
+ result.push({goodExampleScript});
+ }
+ result.push({goodExample});
+ }
+ return result;
+ }, [loadExample, goodExample, goodExampleScript]);
+
+ return (
+
+
+
+
+ {loadExample === SHOW_BAD_EXAMPLE ? 'Hide Bad' : 'Show Bad'}
+
+
+ {loadExample === SHOW_GOOD_EXAMPLE ? 'Hide Good' : 'Show Good'}
+
+
+
+ {loadExample === SHOW_BAD_EXAMPLE
+ ? badExampleContents
+ : loadExample === SHOW_GOOD_EXAMPLE
+ ? goodExampleContents
+ : null}
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ paddingHorizontal: 5,
+ },
+ controls: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ borderBottomWidth: 1,
+ borderColor: 'gray',
+ marginBottom: 5,
+ },
+ exampleContainer: {
+ flex: 1,
+ backgroundColor: 'white',
+ },
+ perfExampleContainer: {},
+});
+
+exports.title = 'Performance Comparison Examples';
+exports.category = 'Basic';
+exports.description =
+ 'Compare performance with bad and good examples. Use React DevTools to highlight re-renders is recommended.';
+exports.examples = [
+ {
+ title: ReRenderWithNonPureChildExample.title,
+ description: ReRenderWithNonPureChildExample.description,
+ render: function (): React.Node {
+ return (
+ }
+ goodExample={}
+ />
+ );
+ },
+ },
+ {
+ title: ReRenderWithObjectPropExample.title,
+ description: ReRenderWithObjectPropExample.description,
+ render: function (): React.Node {
+ return (
+ }
+ goodExample={}
+ />
+ );
+ },
+ },
+];
diff --git a/packages/rn-tester/js/examples/Performance/ReRenderWithNonPureChildExample.js b/packages/rn-tester/js/examples/Performance/ReRenderWithNonPureChildExample.js
new file mode 100644
index 00000000000000..7c3c118233274a
--- /dev/null
+++ b/packages/rn-tester/js/examples/Performance/ReRenderWithNonPureChildExample.js
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow strict-local
+ * @format
+ * @oncall react_native
+ */
+
+'use strict';
+
+import * as React from 'react';
+import {Text} from 'react-native';
+import ItemList from './ItemList';
+import {LIST_100_ITEMS} from './itemData';
+import type {ScrollEvent} from 'react-native/Libraries/Types/CoreEventTypes';
+
+const {useCallback, useState} = React;
+const ItemListMemo = React.memo(ItemList);
+
+function ReRenderWithNonPureChildBadExample(): React.Node {
+ const [scrollOffset, setScrollOffset] = useState(0);
+ const onScroll = useCallback(
+ (evt: ScrollEvent) => {
+ setScrollOffset(evt.nativeEvent.contentOffset.x);
+ },
+ [setScrollOffset],
+ );
+
+ return (
+ <>
+ {`Scroll Offset X: ${scrollOffset}`}
+
+ >
+ );
+}
+
+function ReRenderWithNonPureChildGoodExample(): React.Node {
+ const [scrollOffset, setScrollOffset] = useState(0);
+ const onScroll = useCallback(
+ (evt: ScrollEvent) => {
+ setScrollOffset(evt.nativeEvent.contentOffset.x);
+ },
+ [setScrollOffset],
+ );
+
+ return (
+ <>
+ {`Scroll Offset X: ${scrollOffset}`}
+
+ >
+ );
+}
+
+export default {
+ title: 'List re-render due to not pure or memoized',
+ description:
+ 'The List component is not pure in the bad example. Even though all props are not changed, it will still re-render when parent re-renders.',
+ Bad: ReRenderWithNonPureChildBadExample,
+ Good: ReRenderWithNonPureChildGoodExample,
+};
diff --git a/packages/rn-tester/js/examples/Performance/ReRenderWithObjectPropExample.js b/packages/rn-tester/js/examples/Performance/ReRenderWithObjectPropExample.js
new file mode 100644
index 00000000000000..e2991e724bc863
--- /dev/null
+++ b/packages/rn-tester/js/examples/Performance/ReRenderWithObjectPropExample.js
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow strict-local
+ * @format
+ * @oncall react_native
+ */
+
+'use strict';
+
+import * as React from 'react';
+import {Text} from 'react-native';
+import ItemList from './ItemList';
+import {LIST_100_ITEMS} from './itemData';
+import type {ScrollEvent} from 'react-native/Libraries/Types/CoreEventTypes';
+
+const {useState, useCallback} = React;
+const ItemListMemo = React.memo(ItemList);
+
+function ReRenderWithObjectPropBadExample(): React.Node {
+ const [scrollOffset, setScrollOffset] = useState(0);
+ return (
+ <>
+ {`Scroll Offset X: ${scrollOffset}`}
+ {
+ setScrollOffset(evt.nativeEvent.contentOffset.x);
+ }}
+ />
+ >
+ );
+}
+
+function ReRenderWithObjectPropGoodExample(): React.Node {
+ const [scrollOffset, setScrollOffset] = useState(0);
+ const onScroll = useCallback(
+ (evt: ScrollEvent) => {
+ setScrollOffset(evt.nativeEvent.contentOffset.x);
+ },
+ [setScrollOffset],
+ );
+
+ return (
+ <>
+ {`Scroll Offset X: ${scrollOffset}`}
+
+ >
+ );
+}
+
+export default {
+ title: 'Re-render from new object reference in prop',
+ description:
+ 'Even with pure or memoized child component, if a new object reference is passed down as prop, the child component will still re-render unnecessarily. The onScroll callback is passed without useCallback hook in the bad example and caused performance issues.',
+ Bad: ReRenderWithObjectPropBadExample,
+ Good: ReRenderWithObjectPropGoodExample,
+};
diff --git a/packages/rn-tester/js/examples/Performance/itemData.js b/packages/rn-tester/js/examples/Performance/itemData.js
new file mode 100644
index 00000000000000..f5e60263ae6685
--- /dev/null
+++ b/packages/rn-tester/js/examples/Performance/itemData.js
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow strict-local
+ * @format
+ * @oncall react_native
+ */
+
+const ALL_CHARS =
+ 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+function generateRandomString(length: number = 16): string {
+ let str = '';
+ for (let i = 0; i < length; i++) {
+ str += ALL_CHARS.charAt(Math.floor(Math.random() * ALL_CHARS.length));
+ }
+ return str;
+}
+
+function generateRandomAge(min: number, max: number): number {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+}
+
+function generateRandomName(): string {
+ return 'Joe ' + generateRandomString();
+}
+
+function generateRandomAddress(): string {
+ const city = generateRandomName() + ' City';
+ const state = generateRandomName() + ' State';
+ const country = generateRandomName() + ' Country';
+ return `${city}, ${state}, ${country}`;
+}
+
+export interface ItemDataType {
+ id: string;
+ name: string;
+ address: string;
+ age: number;
+}
+
+export function generateRandomItems(count: number): ItemDataType[] {
+ return Array.from(Array(count), () => ({
+ id: generateRandomString(),
+ name: generateRandomName(),
+ address: generateRandomAddress(),
+ age: generateRandomAge(13, 40),
+ }));
+}
+
+export const LIST_10_ITEMS: ItemDataType[] = generateRandomItems(10);
+export const LIST_100_ITEMS: ItemDataType[] = generateRandomItems(100);
+export const LIST_1000_ITEMS: ItemDataType[] = generateRandomItems(1000);
diff --git a/packages/rn-tester/js/utils/RNTesterList.android.js b/packages/rn-tester/js/utils/RNTesterList.android.js
index 429b19428a9b79..d702ef779ede31 100644
--- a/packages/rn-tester/js/utils/RNTesterList.android.js
+++ b/packages/rn-tester/js/utils/RNTesterList.android.js
@@ -130,6 +130,11 @@ const Components: Array = [
category: 'UI',
module: require('../examples/NewArchitecture/NewArchitectureExample'),
},
+ {
+ key: 'PerformanceComparisonExample',
+ category: 'Basic',
+ module: require('../examples/Performance/PerformanceComparisonExample'),
+ },
];
const APIs: Array = ([
diff --git a/packages/rn-tester/js/utils/RNTesterList.ios.js b/packages/rn-tester/js/utils/RNTesterList.ios.js
index 88c793f68a2ee1..5287ec00761782 100644
--- a/packages/rn-tester/js/utils/RNTesterList.ios.js
+++ b/packages/rn-tester/js/utils/RNTesterList.ios.js
@@ -136,6 +136,11 @@ const Components: Array = [
category: 'UI',
module: require('../examples/NewArchitecture/NewArchitectureExample'),
},
+ {
+ key: 'PerformanceComparisonExample',
+ category: 'Basic',
+ module: require('../examples/Performance/PerformanceComparisonExample'),
+ },
];
const APIs: Array = ([