javascriptreact-nativereact-native-flatlistreact-navigation-v5

React Native performance slow


I am new to React-Native, I have created an Instagram Clone with customisations, but there are some issues.

When the like button is Pressed, FlatList is taking 2 Seconds to re-render. So I tried into Flipkart's RecyclerView Package, that is too taking 400-600 ms. I have came to know that Instagram and Facebook are built at React Native, but they don't take this much time on like. I guess something is wrong in my code.

I got Recycler View package from here

In those screens without any List, there is too an issue of slow re-rendering.

I have found that React Navigations's Material Top navigation is working absolutely fine on swipe, but on button click, it is taking 2-4 Seconds.

Here is my code for feed page.

import React, { useEffect, useState, useRef } from 'react';
import { SafeAreaView, Pressable, AppRegistry, Text, View, Image, TouchableOpacity, StyleSheet, ImageBackground, ActivityIndicator, Platform } from 'react-native';
import { Pranah } from '../pranah/cust';
import { colors } from '../pranah/colors';
import { uni } from '../css/uni';
import axios from 'axios';
import base64 from 'react-native-base64';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { DataProvider, LayoutProvider, RecyclerListView } from 'recyclerlistview';
import { UserHead } from './tminc/userhead';
import { AntDesign, FontAwesome5, Feather } from '@expo/vector-icons';
import { design } from './tminc/design';
import { WebBasedNavigation } from './tminc/widenav'


const style = StyleSheet.create({
    web: {
        width: uni.dev("100%", "100%", "40%"),
        height: uni.dev("100%", "100%", uni.height - 50),
        marginLeft: uni.dev(0, 0, 10 / 100 * uni.width)
    }
});
const postDesign = {
    width: uni.dev(95 / 100 * uni.width, 95 / 100 * uni.width, 35 / 100 * uni.width),
    height: uni.dev(95 / 100 * uni.width, 95 / 100 * uni.width, 35 / 100 * uni.width),
    backgroundColor: "#ededed",
    borderRadius: 10,
}
const iconDynamicSizing = 25;
const iconDesign = StyleSheet.create({
    icon: {
        margin: 10
    }
});
//POST PART IN PARTS
//USER HEAD



function ListHead(txt) {
    return (
        <>
            <Text
                style={{
                    fontSize: 35,
                    fontWeight: "bold",
                    margin: 20
                }}
            >{txt.txt}</Text>
        </>
    )
}
function MediaCont(obj) {
    return (
        <View
            style={design.media}
        >
            <Image
                source={{ uri: obj.url }}
                defaultSource={{ uri: obj.url }}
                style={postDesign}
            />
            <View
                style={design.mediaSnap}
            >
                <Text style={design.mediaCap}>{obj.caption.length > 20 ? `${obj.caption.substring(0, 20)}...` : obj.caption}</Text>
            </View>
        </View>
    );
}
function TextCont(obj) {
    return (
        <View
            style={design.textContParent}
        >
            <View
                style={[postDesign, design.center]}
            >
                <Text
                    style={design.textMain}
                >{obj.caption}</Text>
            </View>
        </View>
    );
}

let layoutProvider = new LayoutProvider(
    index => {
        return index == 0 ? "HEAD" : "NORMAL";
    },
    (type, dim) => {
        switch (type) {
            case "NORMAL":
                dim.height = uni.dev(uni.width + 150, uni.width + 150, 40 / 100 * uni.width + 150);
                dim.width = uni.dev(uni.width, uni.width, 40 / 100 * uni.width);
                break;
            case "HEAD":
                dim.height = 85;
                dim.width = uni.dev(uni.width, uni.width, 40 / 100 * uni.width);
                break;

        }
    }
);

function PostLikes(obj) {
    let post = obj.postId;
    let like = parseInt(obj.like);
    let navigation = obj.screenNav;
    let toprint;
    if (like == 0) {
        toprint = uni.lang("इसे पसंद करने वाले पहले व्यक्ति बनें", "Be first to like this.");
    } else if (like == 1) {
        toprint = uni.lang("एक व्यक्ति द्वारा पसंद किया गया", "Liked by one person");
    } else {
        like = String(like);
        toprint = uni.lang(`${like} लोगो ने पसंद किया`, `${like} likes`);
    }
    return (
        <>
            <TouchableOpacity
                onPress={() => {
                    navigation.push('LikeList', { postId: post });
                }}
            >
                <Text
                    style={{
                        marginLeft: uni.dev(5 / 100 * uni.width, 5 / 100 * uni.width, 4 / 100 * uni.width),
                        fontWeight: "bold",
                        marginTop: 5
                    }}
                >{toprint}</Text>
            </TouchableOpacity>
        </>
    );
}

const headerComp = ({
    title: uni.lang("सबकुछ ||", "Everything."),
    type: "head"
});

export function Feed({ navigation }) {
    const [List, setData] = useState([headerComp]);
    const [FooterConst, setFoot] = useState(true);
    const [start, setStart] = useState(0);
    // navigation.setOptions({ tabBarVisible: false });

    let dataProvider = new DataProvider((r1, r2) => {
        return r1 !== r2;
    }).cloneWithRows(List);


    function fetchMore() {
        AsyncStorage.getItem("mail")
            .then((val) => {
                let mail = val;
                AsyncStorage.getItem("pass")
                    .then((value) => {
                        let pass = value;
                        //   CONNECTING TO SERVER
                        axios({
                            method: 'post',
                            url: uni.bind('feed'),
                            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                            data: uni.string({
                                mail: mail,
                                pass: base64.encode(pass),
                                start: start
                            })
                        })
                            .then((resp) => {
                                if (resp.status == 200) {
                                    let page = resp.data;
                                    /*
                                        SERVER RETURNS
                                        nomore | followernull | error | invalid | {json data}
                                    */
                                    if (uni.logic(page) === "error") {
                                        uni.Error();
                                    } else if (uni.logic(page) === "followernull" || uni.logic(page) === "nomore") {
                                        //SET FOOTER
                                        setFoot(false);
                                    } else if (uni.logic(page) === "invalid") {
                                        //SIGNOUT
                                        uni.signOut(navigation);
                                    } else {
                                        setStart(start + 20);
                                        setData(
                                            [
                                                ...List,
                                                ...page
                                            ]
                                        );
                                    }
                                } else {
                                    uni.Error();
                                }
                            })
                            .catch((e) => {
                                uni.Error();
                            });

                    })
                    .catch((e) => { uni.signOut(navigation) })
            })
            .catch(() => { uni.signOut(navigation) })
    }


    function PostAction(obj) {
        let index = obj.in;
        function addRemoveLike() {
            let temp = List;
            temp[index].liked = temp[index].liked === "true" ? "false" : "true";
            // console.warn(temp[index]);
            setData([...temp]);

            //SAVING LIKE ON SERVER
            AsyncStorage.getItem("mail")
                .then((val) => {
                    let mail = val;
                    AsyncStorage.getItem("pass")
                        .then((value) => {
                            let pass = value;
                            //   CONNECTING TO SERVER
                            axios({
                                method: 'post',
                                url: uni.bind('like'),
                                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                                data: uni.string({
                                    mail: mail,
                                    pass: base64.encode(pass),
                                    post: String(obj.id)
                                })
                            })
                                .then((resp) => {
                                    if (resp.status == 200) {
                                        let page = resp.data;
                                        /*
                                            SERVER RETURNS
                                            true | error | invalid
                                        */
                                        if (uni.logic(page) === "error") {
                                            uni.Error();
                                        } else if (uni.logic(page) === "invalid") {
                                            uni.signOut(navigation);
                                        }
                                    } else {
                                        uni.Error();
                                    }
                                })
                                .catch((e) => { uni.Error() });
                        })
                        .catch((e) => { uni.signOut(navigation) })
                })
                .catch(() => { uni.signOut(navigation) })
        }

        return (
            <>
                <View
                    style={design.postActionParent}
                >
                    <TouchableOpacity
                        onPress={() => {
                            // console.warn(likeRef.current);
                            // console.warn(likeRef.current);
                            addRemoveLike();
                        }}
                    ><AntDesign name={obj.liked === "true" ? "heart" : "hearto"} size={iconDynamicSizing} color="black" style={iconDesign.icon} /></TouchableOpacity>
                    <TouchableOpacity onPress={() => {
                        navigation.push('Comment', { postId: obj.id });
                    }}><FontAwesome5 name="comment" size={iconDynamicSizing} color="black" style={iconDesign.icon} /></TouchableOpacity>
                    <TouchableOpacity><Feather name="send" size={iconDynamicSizing} color="black" style={iconDesign.icon} /></TouchableOpacity>
                    <TouchableOpacity><AntDesign name="retweet" size={iconDynamicSizing} color="black" style={iconDesign.icon} /></TouchableOpacity>
                </View>
                <View
                    style={design.underLinePrnt}
                >
                    <View style={design.underline}></View>
                </View>
            </>
        );
    }

    function TextPost(params) {
        let item = params.data;
        let index = params.in;
        return (
            <>
                <UserHead dp={item.dp} name={item.name} user={item.username} />
                <Pressable onLongPress={() => { alert('null') }}><TextCont caption={item.caption} /></Pressable>
                <PostLikes like={item.fav} postId={item.id} screenNav={navigation} />
                <PostAction liked={item.liked} in={index} id={item.id} />
            </>
        );
    }
    function MediaPost(params) {
        let item = params.data;
        let index = params.in;
        return (
            <>
                <UserHead dp={item.dp} name={item.name} user={item.username} />
                <MediaCont url={item.url} caption={item.caption} />
                <PostLikes like={item.fav} postId={item.id} screenNav={navigation} />
                <PostAction liked={item.liked} in={index} id={item.id} />
            </>
        );
    }

    function ListItem(type, data, index) {
        let item = data;
        return item.type === "head" ? <ListHead txt={item.title} /> : item.type === "text" ? <TextPost data={item} in={index} /> : <MediaPost data={item} in={index} />;
    }

    useEffect(function () {
        // let tmp = List.push(json);
        // setData([
        //     ...List,
        //     ...json
        // ]);
        navigation.setOptions({ tabBarVisible: uni.isPC() == true ? false : true })
        fetchMore();
    }, []);

    function footerComp() {
        return FooterConst == true ? (
            <>
                <ActivityIndicator size={"large"} color={colors.primary} />
                <Pranah.br height={20} />
            </>) : (
            <>
                <Text
                    style={{
                        textAlign: "center",
                        width: "100%",
                        fontSize: 20,
                        fontWeight: "bold",
                        paddingBottom: 13
                    }}
                >{uni.lang("सूची का अंत", "End of Posts")}</Text>
            </>
        );
    }

    return (
        <SafeAreaView style={{ flex: 1, backgroundColor: "#FFFFFF" }}>
            <ImageBackground
                style={{
                    width: "100%",
                    height: "100%"
                }}
                source={require('../assets/background_mobile.png')}
            >
                <Pranah.stb />
                <Pranah.pranahHead nav={navigation} />
                <View
                    style={{ width: "100%", height: "100%", flexDirection: "row" }}
                >
                    <View
                        style={style.web}
                    >
                        <RecyclerListView
                            dataProvider={dataProvider}
                            rowRenderer={ListItem}
                            layoutProvider={layoutProvider}
                            extendedState={{ List }}
                            renderFooter={footerComp}
                            onEndReached={fetchMore}
                        />
                    </View>
                    <WebBasedNavigation navigation={navigation} />
                </View>
            </ImageBackground>
        </SafeAreaView>
    );
}

There were lags in iOS and Web too but those were acceptable.

I know, I've done very wrong with AsyncStorage, please tell me a short way to do that too.

Thanks in advance.


Solution

  • In your case, I don't know why you are using another package when react-native contains a built-in component called as FlatList which is backed by virtualised rendering.

    Make this changes

    rowRenderer={() => ListItem()}
    renderFooter={() => footerComp()}
    

    Check the () => arrow function this will assign the method only once on the initial render. You need to provide a https://reactnative.dev/docs/flatlist#keyextractor prop to create a unique ID for all the rendered items (will be used when you want to do some action like remove element or update).

    With this simple change, you should see a lot of performance improvement for the initial render & for each re-render.

    Do the same for the props that accept a function as a param.

    IDK why are you storing the value on async storage they should be store in a local variable like the useState hook. Keep an eye on the API call if you do frequent API calls or on each re-render surely it will reduce the app performnce.

    My Opinion

    React & React Native are fast by default but developers use a lot of anti-pattern code and make the application slow and complain RN is slow.

    Here you can find some of the common things which cause performance issues in react native.

    https://reactnative.dev/docs/performance