skip to Main Content

I have a case with a Flatlist where users can scroll down to load more rows. I do not know if this is the correct way to check if components are re-rendering, but I added a log statement in the component to see how often it is triggered. Right now, when I load more rows, previous rows are re-rendered, therefore making the process slow as more rows are added.

This is what I am doing so far. I thought using memo would solve this issue, but I don’t think that is working for me. Ideally, I want the componentB to render just once although I am aware useEffect in componentB may cause re-rendering. Even if I am changing totally unrelated state variable, all ComponentB are re-rendered

ComponentA = () => {
  const [x, setX] = useState([]);
  
  const [y, setY] = useState(0);
  

  const loadMore = () => {
     ...load more
     setX(newX);

     //tested to see if setting y will trigger re-render which it did
     setY(1);
  }

  return <FlatList
    data={x}
    keyExtractor={item => item.id}
    onEndReached={loadMore}
    renderItem={({ item }) => { return <ComponentB item={item}> })
  >
}
ComponentB = ({item}) => {
  useEffect(() => {
     fetch some info using item
  })
  return ...
}

export default memo(ComponentB);

Also, when I navigate to a screen and then come back, componentB are re-rendered although nothing has changed. Note that I tried using memo(ComponentB, customComparator) to check for equality.

2

Answers


  1. If you are in a hurry: https://snack.expo.dev/BItGvjw7S?platform=android

    Answer with description

    We should move the FlatList into its own component

    import { FlatList, View } from "react-native"
    import ComponentB from "./componentB" // put your path here please
    
    const MainList = (props) => {
      return (
        <View>
          <FlatList
            data={props.data}
            onEndReachedThreshold={props.onEndReachedThreshold}
            keyExtractor={(_, index) => index}
            refreshing={props.refreshing}
            onEndReached={props.onEndReached}
            refreshControl={props.refreshControl}
            renderItem={({ item }) => <ComponentB item={item} />}
          />
        </View>
      )
    }
    
    export default MainList
    

    Then we should wrap our ComponentB with memo

    import React from "react"
    import { StyleSheet, Text, View } from "react-native"
    
    const componentB = React.memo(({item}) => {
      console.log('componentB rendered', item)
    
      return (
        <View style={styles.item}>
          <Text style={styles.title}>SS</Text>
        </View>
      )
    })
    
    const styles = StyleSheet.create({
        item: {
            backgroundColor: '#f9c2ff',
            padding: 50,
            marginVertical: 50,
        },
        title: {
            fontSize: 12,
        },
    })
    
    export default componentB
    

    Finally, we should call our MainList component from the App component

    import * as React from 'react'
    import { RefreshControl, StyleSheet, Text, View } from 'react-native'
    import Constants from 'expo-constants'
    
    import MainList from "./components/mainList" // put your path here please
    
    export default function App() {
      const [x, setX] = React.useState([])
    
      const [loading, setLoading] = React.useState(true)
      const [refreshing, setRefreshing] = React.useState(false)
      const [showRefreshingIndicator, setShowRefreshingIndicator] = React.useState(false)
    
      const dataIndex = React.useRef(0)
      const totalHits = React.useRef(30)
      
      const loadMore = React.useCallback(async (reset: boolean) => {
        if (reset === true) dataIndex.current = 0
    
        if (dataIndex.current !== 0 && dataIndex.current >= totalHits.current) return {}
    
        const fakepage = Math.round(Math.random()) * 2
        const resultObject = await fetch(`https://reqres.in/api/users?page=${fakepage}`)
        const result = await resultObject.json()
        
        dataIndex.current++
    
        return {
          myIndex: `${dataIndex.current-1}`,
          data: await result.data
        }
      }, [])
    
      const getInitialData = React.useCallback(async () => {
        const list = await loadMore(false)
        if(!list) return
          setX([list])
    
        setLoading(false)
      }, [loadMore])
    
      React.useEffect(() => {
        getInitialData()
      }, [getInitialData])
    
      const onEndReachedHandler = React.useCallback(async () => {
        const newItems = await loadMore(false)
        
        if(Object.keys(newItems).length == 0) return
          setX([...x, newItems])
      }, [x, loadMore])
    
      const onEndReached = React.useCallback(async () => {
        if(refreshing) return
        
        setRefreshing(true)
        
        onEndReachedHandler().then(() => {
          setRefreshing(false)
        })
      }, [refreshing, onEndReachedHandler])
    
      const onRefresh = React.useCallback(async () => {
        setShowRefreshingIndicator(true)
        const newItems = await loadMore(true)
        setX([newItems])
    
        setShowRefreshingIndicator(false)
    
      }, [loadMore])
    
      return (
        <View style={styles.container}>
          <Text style={styles.paragraph}>
            FlatList Example
          </Text>
          
          <MainList 
            data={x}
            refreshing={refreshing}
            refreshControl={
              <RefreshControl refreshing={showRefreshingIndicator} onRefresh={onRefresh} />
            }
            onEndReachedThreshold={1}
            onEndReached={onEndReached}
          />
        </View>
      )
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        paddingTop: Constants.statusBarHeight,
        backgroundColor: '#ecf0f1',
        padding: 8,
      },
      paragraph: {
        margin: 24,
        fontSize: 18,
        fontWeight: 'bold',
        textAlign: 'center',
      },
    })
    

    To see it in action, go to this link and click the ‘Tap to play’ button please. When the application runs, slowly scroll to the bottom and see console messages on the LOGS tab. If you do not see the LOGS tab, click on the bar with ‘No errors’ text under your browser window to open it up. I think this is what exactly you want.

    NOTE: To make things faster, I got the FlatList implementation ready from an answer on StackOverflow and tweaked the code according to your needs.

    Login or Signup to reply.
  2. using memo was a good solution but not enough, you need the remove any anonymous function because it will be recreated on each render which will cause performance issues sometimes.

    For example:

    const renderItem = useCallback(({item}) => (
       <View key={item.key}>
          <Text>{item.title}</Text>
       </View>
     ), []);
    
    const keyExtractor =useCallback(({item}) => item.id, []);
    
    return (
      // ...
    
      <FlatList data={items} keyExtractor={keyExtractor} renderItem={renderItem} />;
      // ...
    );
    

    Refer to this : https://reactnative.dev/docs/optimizing-flatlist-configuration#avoid-anonymous-function-on-renderitem

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search