/** * 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 {generateRandomItems} from '../components/itemData'; import ItemList from '../components/ItemList'; import * as React from 'react'; import {StrictMode, useCallback, useEffect, useState} from 'react'; import {Text} from 'react-native'; 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, };