Home > Mobile >  How to render 200 view without performance issue in react-native
How to render 200 view without performance issue in react-native

Time:08-14

I am trying to make a game in react-native. I want to render 200 views on the Game screen. Each View has a pressable functionality. Whenever I press the View I need to run a function that will change the View background color and update score on the game context. But Whenever I try to press any View it took some time to change the background and update the context.

Note

I am using the expo as a development environment and I am using a real device too.

My View Component

import { useEffect, useState, memo } from "react";
import { useContext } from "react";
import { gameContext } from "./gameContext";
import { Pressable, View } from "react-native";
function CheckBoxCom() {
  const [active, setActive] = useState(false);
  const { score, setScore } = useContext(gameContext);
  useEffect(() => {
    let time = setTimeout(() => {
      setActive(false);
    }, Math.floor(Math.random() * 35000));

    return () => clearTimeout(time);
  }, [active]);
  const handlePress = () => {
    if (active) return;
    setActive(true);
    setScore(score   1);
  };

  return (
    <View>
      <Pressable onPress={handlePress}>
        <View
          style={{
            width: 20,
            height: 20,
            borderWidth: 2,
            borderColor: active ? "green" : "gray",
            margin: 3,
            borderRadius: 3,
            backgroundColor: active ? "green" : null,
          }}
        ></View>
      </Pressable>
    </View>
  );
}

export default memo(CheckBoxCom);

Game Screen Component

import { useContext, useEffect, useState } from "react";
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View, FlatList } from "react-native";
import CheckBox from "./CheckBox";
import { gameContext } from "./gameContext";

export default function Game({ navigation }) {
  const { score, time, setTime, boxList } = useContext(gameContext);
  const [intervalId, setIntervalId] = useState("");

  useEffect(() => {
    const int = setInterval(() => {
      setTime((prvTime) => prvTime - 1);
    }, 1000);
    setIntervalId(int);
    return () => clearInterval(int);
  }, []);

  if (time === 0) {
    clearInterval(intervalId);
    navigation.navigate("Score", { score });
  }

  return (
    <View style={{ flex: 1 }}>
      <StatusBar style="auto" />
      <View style={styles.textHeader}>
        <Text>Score : {score}</Text>
        <Text>Time Left: {time}s</Text>
      </View>
      <View style={styles.checkBoxContainer}>
        <FlatList
          style={{ alignSelf: "center" }}
          data={boxList}
          initialNumToRender={50}
          numColumns={12}
          renderItem={(i) => <CheckBox />}
          keyExtractor={(i) => i.toString()}
          scrollEnabled={false}
        />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  textHeader: {
    display: "flex",
    flexDirection: "row",
    justifyContent: "space-between",
    width: "100%",
    marginTop: 40,
    paddingHorizontal: 30,
  },
  checkBoxContainer: {
    margin: 20,
    display: "flex",
    flexWrap: "wrap",
    height: "80%",
    overflow: "hidden",
    flexDirection: "row",
  },
});

How can I run view function immediately whenever I press it?

CodePudding user response:

The reason it is slow is that when you press on a view, all 200 CheckBoxCom components rerender. If they don't need to, we can improve performance by trying to prevent those unnecessary rerenders.

I believe the major bottleneck here is the gameContext. It groups together a lot of states and if any of these were to change, all components will rerender. It provides score state that you are reading within each CheckBoxCom. Whenever the score changes all CheckBoxCom components will re-render. If you change handlePress() to:

const handlePress = () => {
  if (active) return;
  setActive(true);
  setScore(score => score   1);
};

Please note the use of callback to update the score in the above handler. In this case, we don't need to read score from context, so we can remove it from the game context provider, only pass setScore. Removing score from the context provider is important because not doing so will rerender all components using the context even if you don't specifically destructure score.

Also, make sure you don't have a lot of state variables within a single context. Split it into multiple contexts if you have different states in there. In this way, you will be able to reduce unnecessary rerenders of the CheckBoxCom components.

Since your CheckBoxCom components have an internal state, using React.memo() will not help to prevent rerenders because it only works for rerenders resulting from changed props.

But if you are able to refactor them to lift the active state up to the parent i.e. something like activeViews or something (which could be a map of indexes which are true i.e. active), then you can pass the active state as a boolean prop to each CheckBoxCom component. And if we also pass setScore via a prop instead of via context, we can benefit from React.memo(). BTW it is not necessary to wrap setState methods with useCallback().

The end result will be: CheckBoxCom components with zero internal states and no reliance on context, in other words, pure components i.e. components which work nicely with React.memo().

CodePudding user response:

Use pagination in flatlist

for ref: Pagination in flatlist

import React, { Component } from 'react';
import {
  View,
  Text,
  TouchableOpacity,
  StyleSheet,
  FlatList,
  Platform,
  ActivityIndicator,
} from 'react-native';

export default class App extends Component {
  constructor() {
    super();
    this.state = {
      loading: true,
      //Loading state used while loading the data for the first time
      serverData: [],
      //Data Source for the FlatList
      fetching_from_server: false,
      //Loading state used while loading more data
    };
    this.offset = 0;
    //Index of the offset to load from web API
  }
 
  componentDidMount() {
    //fetch('http://aboutreact.com/demo/getpost.php?offset='   this.offset)
    fetch('https://www.doviz.com/api/v1/currencies/all/latest')
      .then(response => response.json())
      .then(responseJson => {
       responseJson = responseJson.slice((this.offset*12),((this.offset 1)*12)-1)
                 console.log("offset : " this.offset);

         console.log(responseJson.slice((this.offset*12),((this.offset 1)*12)-1));
      //Successful response from the API Call 
        this.offset = this.offset   1;
        //After the response increasing the offset for the next API call.
        this.setState({
         // serverData: [...this.state.serverData, ...responseJson.results],
         serverData: [...this.state.serverData, ...responseJson],
          //adding the new data with old one available in Data Source of the List
          loading: false,
          //updating the loading state to false
        });
      })
      .catch(error => {
        console.error(error);
      });
  }
 
  loadMoreData = () => {
  //On click of Load More button We will call the web API again
    this.setState({ fetching_from_server: true }, () => { 
      //fetch('http://aboutreact.com/demo/getpost.php?offset='   this.offset)
      fetch('https://www.doviz.com/api/v1/currencies/all/latest')
          .then(response => response.json())
          .then(responseJson => {
           responseJson = responseJson.slice((this.offset*12),((this.offset 1)*12)-1)
            console.log("offset Load : " this.offset);
          console.log(responseJson);
          //Successful response from the API Call 
            this.offset = this.offset   1;
            
            //After the response increasing the offset for the next API call.
            this.setState({
              //serverData: [...this.state.serverData, ...responseJson.results],
              serverData: [...this.state.serverData, ...responseJson],
              fetching_from_server: false,
              //updating the loading state to false
            });
          })
          .catch(error => {
            console.error(error);
          });
    });
  };

  renderFooter() {
    return (
    //Footer View with Load More button
      <View style={styles.footer}>
        <TouchableOpacity
          activeOpacity={0.9}
          onPress={this.loadMoreData}
          //On Click of button calling loadMoreData function to load more data
          style={styles.loadMoreBtn}>
          <Text style={styles.btnText}>Loading</Text>
          {this.state.fetching_from_server ? (
            <ActivityIndicator color="white" style={{ marginLeft: 8 }} />
          ) : null}
        </TouchableOpacity>
      </View>
    );
  }

  render() {
    return (
      <View style={styles.container}>
        {this.state.loading ? (
          <ActivityIndicator size="large" />
        ) : (
          <FlatList
            style={{ width: '100%' }}
            keyExtractor={(item, index) => index}
            data={this.state.serverData}
            renderItem={({ item, index }) => (
              <View style={styles.item}>
                <Text style={styles.text}>
                  {item.currency}
                  {'.'} 
                  {item.code}
                </Text>
              </View>
            )}
            onEndReached={this.loadMoreData}
            onEndReachedThreshold ={0.1}
            ItemSeparatorComponent={() => <View style={styles.separator} />}
            ListFooterComponent={this.renderFooter.bind(this)}
            //Adding Load More button as footer component
          />
        )}
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    paddingTop: 30,
  },
  item: {
    padding: 10,height:80
  },
  separator: {
    height: 0.5,
    backgroundColor: 'rgba(0,0,0,0.4)',
  },
  text: {
    fontSize: 15,
    color: 'black',
  },
  footer: {
    padding: 10,
    justifyContent: 'center',
    alignItems: 'center',
    flexDirection: 'row',
  },
  loadMoreBtn: {
    padding: 10,
    backgroundColor: '#800000',
    borderRadius: 4,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
  },
  btnText: {
    color: 'white',
    fontSize: 15,
    textAlign: 'center',
  },
});

  • Related