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?
2
Answers
Use pagination in flatlist
for ref: Pagination in flatlist
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 providesscore
state that you are reading within eachCheckBoxCom
. Whenever the score changes allCheckBoxCom
components will re-render. If you changehandlePress()
to: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 passsetScore
. Removingscore
from the context provider is important because not doing so will rerender all components using the context even if you don’t specifically destructurescore
.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, usingReact.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 likeactiveViews
or something (which could be a map of indexes which aretrue
i.e. active), then you can pass theactive
state as a boolean prop to eachCheckBoxCom
component. And if we also passsetScore
via a prop instead of via context, we can benefit fromReact.memo()
. BTW it is not necessary to wrapsetState
methods withuseCallback()
.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 withReact.memo()
.