How to Use useCallback() Hook Efficiently in FlatList

1.4k Views Asked by At

I just recently learned how to use useCallback() hook. Right now I have an application to render a component called "Events" in the FlatList React Native component. I looked up posts on how to use useCallback() in a FlatList component and I implemented as I have my code below. However, I still received this warning message: VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc. Object { "contentLength": 3362, "dt": 307776, "prevDt": 6216,}. I intepreted that if I received this warning message, it is very likely that I did not implement this hook right. How can I implement it correctly? I appreciate all helps I can get to help me to learn how to use this hook efficiently. Thank you!

Here is my HomeScreen component in which I have the FlatList component:

import React, { useState, useRef, useEffect, useCallback } from 'react'
import { FlatList, StyleSheet, View, Text, TouchableOpacity, Image, RefreshControl } from 'react-native'
import { auth } from '../Firebase'
import Event from "./Event";
import { getFeed, getEventsByUserName } from "./services/EventsServices";
import useStore from "../store";
import { container } from './style';
import { useNavigation } from "@react-navigation/core";

const HomeScreen = (props) => {
    const [eventList, setEventList] = useState([]);
    const [refresh, setRefresh] = useState(false);
    const navigation = useNavigation();

    const refresh_feed = () => {
        if (props.route.params.isMyFeed) {
            getEventsByUserName(useStore.getState().userName).then(events => {
                setEventList(events);
            }).catch(error => console.log(error))
        }
        else {
            getFeed().then(events => {
                setEventList(events);
            }).catch(error => console.log(error))
        }
    }

    // synchronize the local refresh state with the global refresh state
    useEffect(() => {
        useStore.setState({
            refresh: false
        })
        refresh_feed();
    }, [refresh])

    useEffect(() => {
        useStore.subscribe(() => setRefresh(true), state => state.refresh)
    }, [setRefresh])


    const handleSignOut = () => {
        useStore.setState({ loggedIn: false, refresh: false, userName: null, userId: null, events: [] })
        auth
            .signOut()
            .then(() => {
                navigation.replace("Login")
            })
            .catch(error => alert(error.message))
    }

    const renderItem = useCallback(({ item, index }) => {
        return (
            <View key={index}>
                <Event route={{ params: { item, index, user: item.userName, isMyFeed: props.route.params.isMyFeed } }} name="Event" navigation={navigation} />
            </View>
        )
    },[])

    return (

        <View style={container.container}>
            <FlatList
                refreshControl={
                    <RefreshControl
                        refreshing={useStore.getState().refresh}
                        onRefresh={() => {
                            refresh_feed();
                        }}
                    />
                }
                viewabilityConfig={{
                    waitForInteraction: false,
                    viewAreaCoveragePercentThreshold: 70,
                }}
                data={eventList}
                renderItem={renderItem}
                numColumns={1}
                horizontal={false}
                keyExtractor={(item) => item.media_id}
                contentContainerStyle={{
                    padding: 20,
                    paddingTop: 42
                }}
            />
        </View>
    )
}

export default HomeScreen

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center'
    },
    event: {
        backgroundColor: '#F0F8FF',
        width: '100%',
        padding: 15,
        borderRadius: 10,
        borderColor: '#0782F9',
        alignItems: 'center',
        marginTop: 40,
    },
    eventText: {
        color: 'black',
        fontWeight: '700',
        fontSize: 16,
    },
    button: {
        backgroundColor: '#0782F9',
        width: 'auto',
        padding: 15,
        borderRadius: 10,
        alignItems: 'center',
        marginTop: 40,
    },
    buttonText: {
        color: 'white',
        fontWeight: '700',
        fontSize: 16,
    },
    separatorStyle: {
        height: 0.5,
        width: '100%',
        backgroundColor: '#C8C8C8',
    },
    avatarImage: {
        width: 70,
        height: 70,
        borderRadius: 70
    },
    eventImage: {

    }
})

Here is my Event component:

import { useEffect, useState, memo } from "react";
import {ImageBackground, View, Text, TouchableOpacity, Image} from "react-native";
import { utils, container, text } from "./style";
import { Feather, FontAwesome5 } from "@expo/vector-icons";
import useStore from "../store";
import { getUserByUserName } from "./services/userServices";


const Event = (props) => {
    const [event, setEvent] = useState(props.route.params.item);
    const [eventUser, setEventUser] = useState("");
    const [eventUserSet, setEventUserSet] = useState(false);


    useEffect(() => {
        if (event.hasOwnProperty("userName")) {
            getUserByUserName(event.userName).then(querySnapShot => {
                querySnapShot.forEach(doc => {
                    setEventUser(doc.data());
                });
            });
            setEventUserSet(true);
        }
        else {
            setEventUser(event.user_id);
            setEventUserSet(true);
        }
    }, [])

    if (props.route.params.item.media !== undefined && eventUserSet) {
        return (
            <View>
                <EventCard params={{ passedRoute: props.route, event: event, eventUser: eventUser, isMyFeed: props.route.params.isMyFeed }} navigation={props.navigation} />
            </View>

        )
    } else {
        return (
            <View>
            </View>
        )
    }
}

const EventCard = (props) => {
    const openDetailedView = () => {
        props.navigation.navigate('Details', {
            passedParams: {passedRoute: props.params.passedRoute, event: props.params.event, eventUser: props.params.eventUser, isMyFeed: props.params.isMyFeed},
        })
    }
    const avatarImage = props.params.eventUser.image === 'default' ?
            (
                <FontAwesome5
                    style={utils.profileImageSmall}
                    name={'user-circle'} size={70} color={'black'} />

            )
            :
            (
                <Image
                    style={utils.profileImageSmall}
                    source={{ uri: props.params.eventUser.image }}
                />
            )

    return(
        //real JSX returned
        <View
            style = {{ marginBottom:20, borderRadius: 16, backgroundColor:"gray"}}>
            <ImageBackground
                source={{ uri: props.params.event.media}}
                style = {{
                    flexDirection:'row',
                    padding: 10,
                    paddingTop:30,
                    paddingBottom: 20,
                    marginBottom:20,
                    height:180,
                    borderRadius: 16,
                    flex : 1,
                    resizeMode: 'cover'}}>
                {/*//Event display*/}
                <TouchableOpacity
                    //TODO::change the width dynamically
                    style = {{
                        flexDirection:'row',
                        height:130,
                        width:330,
                        backgroundColor: 'rgba(255, 255, 255, 0.73)',
                        padding: 20,
                        color: '#fff',
                        fontSize: 26
                    }}
                    onPress={openDetailedView}
                >
                    <View>
                        {avatarImage}
                    </View>
                    <View>
                        <Text style = {{fontSize: 22, fontWeight:'700',width:110,height: 30, display: "flex"}}>{props.params.event.eventTitle}</Text>
                        <Text style = {{fontSize: 14, opacity: .7, width:90, height: 40, display:"flex"}}>{props.params.event.eventDescription}</Text>
                        <Text style = {{fontSize: 12, opacity: .8, color : '#0099cc'}}> {"End date: "} {props.params.event.eventEndDate}</Text>
                        <Text style = {{fontSize: 12, opacity: .8, color : '#0099cc'}}>
                            {"  Loc: "} {props.params.event.eventLocation}
                        </Text>
                    </View>
                    {/*//the place of it is moving with content*/}
                    <View style={[utils.padding10, container.horizontal,{
                        paddingTop:35
                    }]}>
                        <Feather style={utils.margin15Left} name={"message-square"} size={50} color={"black"}
                                 onPress={() => props.navigation.navigate("Comment", {
                                     eventId: props.params.passedRoute.params.item.id,
                                     user: useStore.getState().userName
                                 })} />
                    </View>
                </TouchableOpacity>

            </ImageBackground>
        </View>

    )
}

export default memo(Event);
0

There are 0 best solutions below