New to react native, but I’m learning as I go. I’m currently building an app and I have an issue with my renderItem function being called after each render, causing my flatlist to have duplicates. I tried to memoize renderItem but when I call memorizedrenderItem, I get an error saying “rendered more hooks than previous render”. How can I fix this? I ultimately just want my flatlist to render the appropriate amount of items, not all the duplicates. I’ve attached a snippet and then my full code.
const renderItem = () => {
console.log("renderItem called");
return result.map((recipe, index) => (
// when you press this, go to a page that displays recipe details
<TouchableOpacity
onPress={() =>
navigation.navigate("viewRecipeScreen", {
recipe: recipe,
})
}
key={index}
>
<Image
source={{
uri:
recipe?.imagepath === "empty"
? "https://static.vecteezy.com/system/resources/previews/005/337/799/original/icon-image-not-found-free-vector.jpg"
: recipe?.imagepath,
}}
style={styles.recipeImage}
/>
</TouchableOpacity>
));
};
const memorenderItem = useMemo(() => renderItem(), []);
import React, {
useEffect,
useContext,
useState,
useCallback,
useMemo,
} from "react";
import {
View,
Text,
StyleSheet,
TextInput,
Image,
FlatList,
TouchableOpacity,
} from "react-native";
import Button from "react-native-button";
import { auth } from "../firebase/firebase";
import { onAuthStateChanged, signInWithEmailAndPassword } from "firebase/auth";
import { doc, getDoc, updateDoc, arrayUnion } from "firebase/firestore";
import { db } from "../firebase/firebase";
import { AuthContext } from "../navigation/AuthProvider";
export function UserHome({ navigation }) {
const { user, setName, setUserName, userName, setBioText, logout } =
useContext(AuthContext);
const [queryText, setQueryText] = useState("");
const [result, setResult] = useState([]);
const [loading, setLoading] = useState(true);
const [searchPressed, setSearchPressed] = useState(false);
const [likes, setLikes] = useState("");
const [dislikes, setDislikes] = useState("");
const [survey, setSurvey] = useState(null);
const handleSignOut = () => {
logout();
};
const handleInputChange = (text) => {
setQueryText(text);
};
useEffect(() => {
const getName = async (userId) => {
const docRef = doc(db, "userInfo", userId);
const docSnap = await getDoc(docRef);
const full_name = docSnap.get("fullname");
const user_name = docSnap.get("username");
const bio_Text = docSnap.get("bio");
const likes = docSnap.get("faveRecs");
setLikes(likes);
const dislikes = docSnap.get("badRecs");
const surveyResults = docSnap.get("tolerance_questionnaire");
setSurvey(surveyResults);
setDislikes(dislikes);
if (docSnap.exists()) {
setName(full_name);
setUserName(user_name);
setBioText(bio_Text);
} else {
console.log("No username, bio, or full name found");
}
};
onAuthStateChanged(auth, (user) => {
if (user) {
getName(user.uid);
}
});
}, []);
useEffect(() => {
const fetchRecipes = async () => {
try {
// create body for lambda function
const palette_body = {
body: {
recipeCategory: null,
user_info: { likes, dislikes, survey },
},
};
const response = await fetch(
`https://25jg62s1m6.execute-api.us-east-1.amazonaws.com/test/kohoquery/palette_analyzer`,
{
method: "POST",
body: JSON.stringify(palette_body),
headers: {
"Content-Type": "application/json",
},
}
);
const result_data = await response.json();
setResult(JSON.parse(result_data.body));
console.log("yay!");
setLoading(false);
} catch (error) {
console.log("Error parsing JSON data!!!:", error);
//setError(error);
setLoading(false);
}
try {
if (queryText) {
const response = await fetch(
`https://25jg62s1m6.execute-api.us-east-1.amazonaws.com/test/kohoquery?queryText=${queryText}`
);
const data = await response.json();
setSearchResults(data.slice(0, 4));
navigation.navigate("recipeDetailsScreen", {
recipe: data.slice(0, 4),
});
}
} catch (error) {
console.error(error);
console.log("erorrrr");
}
};
fetchRecipes();
}, [searchPressed]);
// I want to fetch 5 recipes from our db
// I want to render those 5 recipes in a slide show to be displayed at all times
//console.log("yayyy again", result[0]);
if (loading) {
return <Text>Loading...</Text>;
}
const renderItem = () => {
console.log("renderItem called");
return result.map((recipe, index) => (
// when you press this, go to a page that displays recipe details
<TouchableOpacity
onPress={() =>
navigation.navigate("viewRecipeScreen", {
recipe: recipe,
})
}
key={index}
>
<Image
source={{
uri:
recipe?.imagepath === "empty"
? "https://static.vecteezy.com/system/resources/previews/005/337/799/original/icon-image-not-found-free-vector.jpg"
: recipe?.imagepath,
}}
style={styles.recipeImage}
/>
</TouchableOpacity>
));
};
const memorenderItem = useMemo(() => renderItem(), []);
return (
<View style={styles.container}>
<View style={styles.headerContainer}>
<Image
source={require("../assets/kohomainlogo.png")}
style={styles.logo}
/>
<View style={styles.welcomeContainer}>
<Text style={styles.welcomeText}>Signed in as: @{userName}!</Text>
<Text style={styles.cookText}>What would you like to cook?</Text>
</View>
<TextInput
style={styles.input}
onChangeText={handleInputChange}
value={queryText}
placeholder="Enter your dish name"
placeholderTextColor={"grey"}
returnKeyType="search"
onSubmitEditing={() => setSearchPressed(!searchPressed)}
/>
</View>
<View style={styles.recipeContainer}>
<Text style={styles.recipeTitle}>We think you'd like these!</Text>
<FlatList
data={result}
renderItem={renderItem}
keyExtractor={(item, index) => index.toString()}
horizontal={true}
windowSize={1}
/>
</View>
<View style={styles.recipeContainerPopular}>
<Text style={styles.recipeTitle}>Most Popular</Text>
</View>
<View style={styles.bottomContainer}>
<Button
containerStyle={{
padding: 10,
height: 45,
overflow: "hidden",
borderRadius: 20,
backgroundColor: "#8252ff",
}}
style={{
fontSize: 18,
color: "white",
textAlign: "center",
}}
onPress={handleSignOut}
>
Sign Out
</Button>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
headerContainer: {
backgroundColor: "#fff",
height: 80,
justifyContent: "center",
alignItems: "center",
borderBottomWidth: 1,
borderBottomColor: "#ccc",
width: "100%",
},
logo: {
width: 150,
height: 75,
resizeMode: "contain",
marginTop: 150,
},
bottomContainer: {
flex: 1,
justifyContent: "flex-end",
marginBottom: 20,
},
welcomeContainer: {
paddingLeft: 10,
},
welcomeText: {
fontSize: 18,
},
cookText: {
fontSize: 18,
fontWeight: "bold",
},
input: {
height: 40,
width: "80%",
borderColor: "gray",
borderWidth: 1,
marginBottom: 20,
padding: 10,
borderRadius: 10,
marginTop: 10,
backgroundColor: "white",
},
recipeContainer: {
flexDirection: "column",
justifyContent: "space-around",
alignItems: "center",
marginTop: 150,
width: "100%",
},
recipeContainerPopular: {
flexDirection: "column",
justifyContent: "space-around",
alignItems: "center",
marginTop: 20,
width: "100%",
},
recipeImage: {
width: 400,
height: 200,
resizeMode: "cover",
borderRadius: 10,
marginHorizontal: 5,
},
recipeTitle: {
fontSize: 24,
fontWeight: "bold",
marginBottom: 10,
marginTop: 10,
textAlign: "left",
alignItems: "flex-start",
},
});
2
Answers
I changed my renderItem function from mapping a list of jsons to just taking in one item at a time.
Both useMemo and useCallback can be used to optimize the rendering performance of a FlatList’s renderItem function, but they serve different purposes.
So if your renderItem function doesn’t depend on any other values or state changes, using useMemo won’t provide any performance benefits.
However, if your renderItem function is a callback function that is called for every item in the data array, using
useCallback
to memoize the function and provide value in the dependency array can provide a performance benefit.example: