diff --git a/packages/rn-tester/js/examples/Performance/performanceComparisonExamples/EffectInRenderExample.js b/packages/rn-tester/js/examples/Performance/performanceComparisonExamples/EffectInRenderExample.js new file mode 100644 index 00000000000..e35e42cc79c --- /dev/null +++ b/packages/rn-tester/js/examples/Performance/performanceComparisonExamples/EffectInRenderExample.js @@ -0,0 +1,131 @@ +/** + * 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 {ItemDataType} from '../components/itemData'; + +import * as React from 'react'; +import {useState, useEffect, useCallback, StrictMode} from 'react'; +import {Text} from 'react-native'; +import {generateRandomItems} from '../components/itemData'; +import ItemList from '../components/ItemList'; + +const TIMEOUT = 500; +const FETCH_COUNT = 250; +const ItemListMemo = React.memo(ItemList); + +function ItemFetcherBadExample(props: { + onFetched: (items: ItemDataType[]) => void, + count: number, +}): React.Node { + const {onFetched, count} = props; + const fetchMoreItems = async ( + itemsCount: number, + ): Promise => { + return new Promise(resolve => { + setTimeout(() => { + resolve(generateRandomItems(itemsCount)); + }, TIMEOUT); + }); + }; + + fetchMoreItems(count).then((items: ItemDataType[]) => { + onFetched(items); + }, console.error); +} + +function ItemFetcherGoodExample(props: { + onFetched: (items: ItemDataType[]) => void, + count: number, +}): React.Node { + const {onFetched, count} = props; + useEffect(() => { + const fetchMoreItems = async ( + itemsCount: number, + ): Promise => { + return new Promise(resolve => { + setTimeout(() => { + resolve(generateRandomItems(itemsCount)); + }, TIMEOUT); + }); + }; + + fetchMoreItems(count).then((items: ItemDataType[]) => { + onFetched(items); + }, console.error); + }, [onFetched, count]); +} + +const ItemFetcherBadExampleMemo = React.memo(ItemFetcherBadExample); +const ItemFetcherGoodExampleMemo = React.memo(ItemFetcherGoodExample); +function EffectInRenderBadExample(): React.Node { + const [visibleItems, setVisibleItems] = useState([]); + const [fetchedItems, setFetchedItems] = useState([]); + const onMoreItemFetched = useCallback( + (items: ItemDataType[]) => { + setFetchedItems(items); + }, + [setFetchedItems], + ); + + if (fetchedItems.length > 0) { + setVisibleItems(visibleItems.concat(fetchedItems)); + setFetchedItems([]); + } + + return ( + + {`Items count in list: ${visibleItems.length}`} + + + + ); +} + +function EffectInRenderGoodExample(): React.Node { + const [visibleItems, setVisibleItems] = useState([]); + const [fetchedItems, setFetchedItems] = useState([]); + const onMoreItemFetched = useCallback( + (items: ItemDataType[]) => { + setFetchedItems(items); + }, + [setFetchedItems], + ); + + if (fetchedItems.length > 0) { + setVisibleItems(visibleItems.concat(fetchedItems)); + setFetchedItems([]); + } + + return ( + + {`Items count in list: ${visibleItems.length}`} + + + + ); +} + +export default { + title: + 'Directly trigger side effect in render may run multiple times and cause error state or re-renders', + description: + 'Trigger a side effect in render method without using effect hook in . This will force render component two times, and the side effect in bad example caused append items to the list two times unexpectedly.', + Bad: EffectInRenderBadExample, + Good: EffectInRenderGoodExample, +}; diff --git a/packages/rn-tester/js/examples/Performance/performanceComparisonExamples/index.js b/packages/rn-tester/js/examples/Performance/performanceComparisonExamples/index.js index 55156aacf04..9381829472b 100644 --- a/packages/rn-tester/js/examples/Performance/performanceComparisonExamples/index.js +++ b/packages/rn-tester/js/examples/Performance/performanceComparisonExamples/index.js @@ -14,3 +14,4 @@ export {default as ReRenderWithObjectPropExample} from './ReRenderWithObjectProp export {default as SetStateInWrongEffectExample} from './SetStateInWrongEffectExample'; export {default as RenderOffscreenContentExample} from './RenderOffscreenContentExample'; export {default as NotMemoizeExpensiveTaskExample} from './NotMemoizeExpensiveTaskExample'; +export {default as EffectInRenderExample} from './EffectInRenderExample';