I’m working on a React Native app and am having trouble rendering the ImageZoom component. Every time i click the button (addItToFavorites), the ImageZoom component re-renders and flashes, which affects the user experience.
Each time i click the addItToFavorites button, it triggers a re-render of the ImageZoom component, causing it to flash momentarily. This is undesirable because it disrupts the user experience.
<code>import React, {useEffect, useLayoutEffect, useState, useCallback} from 'react';
import axios from 'axios';
import {
FlatList,
Image,
Text,
View,
StyleSheet,
ActivityIndicator,
SafeAreaView,
TouchableOpacity,
Share,
Alert,
Linking,
} from 'react-native';
import {useNavigation, useRoute} from '@react-navigation/native';
import {rh, rw} from '../Services/Dimension';
import AppLink from 'react-native-app-link';
import {ImageZoom} from '@likashefqet/react-native-image-zoom';
import AsyncStorage from '@react-native-async-storage/async-storage';
const StepsScreen = () => {
const navigation = useNavigation();
const route = useRoute();
const [guide, setGuide] = useState(null);
const [loading, setLoading] = useState(true);
// const [imageLoading, setImageLoading] = useState({});
const [favoriteIcon, setFavoriteIcon] = useState(false);
const {guideid} = route?.params;
const checkIfFavorite = async () => {
try {
const favoritesString = await AsyncStorage.getItem('favoriteGuides');
const favorites = favoritesString ? JSON.parse(favoritesString) : [];
const isFavorite = favorites.some(fav => fav.guideid === guideid);
setFavoriteIcon(isFavorite);
} catch (error) {
console.log('Error checking favorite guide in AsyncStorage:', error);
}
};
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(
`................`,
);
setGuide(response.data);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
};
fetchData();
checkIfFavorite();
}, [guideid]);
useLayoutEffect(() => {
if (guide?.title) {
navigation.setOptions({
headerTitle:
guide.title.length > 20
? guide.title.slice(0, 19) + '...'
: guide.title,
});
}
}, [guide?.title, navigation]);
if (loading) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#187293" />
</View>
);
}
if (!guide) {
return <Text style={styles.errorText}>No guides available</Text>;
}
const formattedPublishedDate = new Date(
guide.published_date * 1000,
).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
/*
addItToFavorites
*/
const addItToFavorites = async () => {
try {
const favoritesString = await AsyncStorage.getItem('favoriteGuides');
const favorites = favoritesString ? JSON.parse(favoritesString) : [];
const isFavorite = favorites.some(fav => fav.guideid === guideid);
if (isFavorite) {
const updatedFavorites = favorites.filter(
fav => fav.guideid !== guideid,
);
await AsyncStorage.setItem(
'favoriteGuides',
JSON.stringify(updatedFavorites),
);
setFavoriteIcon(false);
} else {
const newFavorite = {guideid, guideTitle: guide.title};
const updatedFavorites = [...favorites, newFavorite];
await AsyncStorage.setItem(
'favoriteGuides',
JSON.stringify(updatedFavorites),
);
setFavoriteIcon(true);
}
} catch (error) {
console.log('Error toggling favorite guide in AsyncStorage:', error);
}
};
const renderToolItem = ({item}) => (
<View style={styles.tableRow}>
........
</View>
);
const renderStepItem = ({item, index}) => (
<View style={styles.stepContainer}>
<View style={styles.stepHeader}>
<Text style={styles.stepNumber}>Step {index + 1}</Text>
<Text
style={{
fontSize: rh(0.02),
color: '#666',
paddingVertical: rh(0.01),
width: rw(0.8),
}}>
{item.title}
</Text>
</View>
{item.media &&
item.media.type === 'image' &&
item.media.data.map((media, mediaIndex) => (
<View key={mediaIndex} style={styles.stepImageContainer}>
<ImageZoom
uri={media?.original}
minScale={0.5}
maxScale={5}
// doubleTapScale={3}
// minPanPointers={1}
// isSingleTapEnabled
// isDoubleTapEnabled
resizeMode="contain"
/>
{/* {imageLoading[media.medium] && (
<ActivityIndicator
style={styles.imageLoader}
size="small"
color="#187293"
/>
)} */}
</View>
))}
{item.lines.map((line, lineIndex) => (
...............
))}
</View>
);
return (
<SafeAreaView style={{flex: 1}}>
<FlatList
showsVerticalScrollIndicator={true}
ListHeaderComponent={() => (
<View style={styles.container}>
<View style={styles.header}>
{guide.image && (
<View>
<Image
source={{uri: guide.image?.medium}}
style={styles.guideImage}
/>
</View>
)}
<View style={styles.headerText}>
............
</View>
<View
style={{
flexDirection: 'row',
alignContent: 'center',
justifyContent: 'space-between',
}}>
<Text style={styles.sectionTitle}>Introduction</Text>
<View style={{flexDirection: 'row'}}>
<TouchableOpacity onPress={addItToFavorites}>
<Image
style={{
width: rh(0.035),
height: rh(0.035),
resizeMode: 'cover',
}}
source={
favoriteIcon
? require('../assets/fullHeart.png')
: require('../assets/emptyHeart.png')
}
/>
</TouchableOpacity>
<TouchableOpacity
onPress={shareApp}
style={{marginLeft: rw(0.05), marginRight: rw(0.02)}}>
<Image
style={{
width: rh(0.033),
height: rh(0.033),
resizeMode: 'cover',
}}
source={require('../assets/share.png')}
/>
</TouchableOpacity>
</View>
</View>
.............
data={guide.tools}
renderItem={renderToolItem}
keyExtractor={(item, index) => index.toString()}
ListFooterComponent={() =>
guide.steps.length > 0 && (
<FlatList
data={guide.steps}
renderItem={renderStepItem}
keyExtractor={item => item.stepid.toString()}
/>
)
}
/>
</SafeAreaView>
);
};
</code>
<code>import React, {useEffect, useLayoutEffect, useState, useCallback} from 'react';
import axios from 'axios';
import {
FlatList,
Image,
Text,
View,
StyleSheet,
ActivityIndicator,
SafeAreaView,
TouchableOpacity,
Share,
Alert,
Linking,
} from 'react-native';
import {useNavigation, useRoute} from '@react-navigation/native';
import {rh, rw} from '../Services/Dimension';
import AppLink from 'react-native-app-link';
import {ImageZoom} from '@likashefqet/react-native-image-zoom';
import AsyncStorage from '@react-native-async-storage/async-storage';
const StepsScreen = () => {
const navigation = useNavigation();
const route = useRoute();
const [guide, setGuide] = useState(null);
const [loading, setLoading] = useState(true);
// const [imageLoading, setImageLoading] = useState({});
const [favoriteIcon, setFavoriteIcon] = useState(false);
const {guideid} = route?.params;
const checkIfFavorite = async () => {
try {
const favoritesString = await AsyncStorage.getItem('favoriteGuides');
const favorites = favoritesString ? JSON.parse(favoritesString) : [];
const isFavorite = favorites.some(fav => fav.guideid === guideid);
setFavoriteIcon(isFavorite);
} catch (error) {
console.log('Error checking favorite guide in AsyncStorage:', error);
}
};
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(
`................`,
);
setGuide(response.data);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
};
fetchData();
checkIfFavorite();
}, [guideid]);
useLayoutEffect(() => {
if (guide?.title) {
navigation.setOptions({
headerTitle:
guide.title.length > 20
? guide.title.slice(0, 19) + '...'
: guide.title,
});
}
}, [guide?.title, navigation]);
if (loading) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#187293" />
</View>
);
}
if (!guide) {
return <Text style={styles.errorText}>No guides available</Text>;
}
const formattedPublishedDate = new Date(
guide.published_date * 1000,
).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
/*
addItToFavorites
*/
const addItToFavorites = async () => {
try {
const favoritesString = await AsyncStorage.getItem('favoriteGuides');
const favorites = favoritesString ? JSON.parse(favoritesString) : [];
const isFavorite = favorites.some(fav => fav.guideid === guideid);
if (isFavorite) {
const updatedFavorites = favorites.filter(
fav => fav.guideid !== guideid,
);
await AsyncStorage.setItem(
'favoriteGuides',
JSON.stringify(updatedFavorites),
);
setFavoriteIcon(false);
} else {
const newFavorite = {guideid, guideTitle: guide.title};
const updatedFavorites = [...favorites, newFavorite];
await AsyncStorage.setItem(
'favoriteGuides',
JSON.stringify(updatedFavorites),
);
setFavoriteIcon(true);
}
} catch (error) {
console.log('Error toggling favorite guide in AsyncStorage:', error);
}
};
const renderToolItem = ({item}) => (
<View style={styles.tableRow}>
........
</View>
);
const renderStepItem = ({item, index}) => (
<View style={styles.stepContainer}>
<View style={styles.stepHeader}>
<Text style={styles.stepNumber}>Step {index + 1}</Text>
<Text
style={{
fontSize: rh(0.02),
color: '#666',
paddingVertical: rh(0.01),
width: rw(0.8),
}}>
{item.title}
</Text>
</View>
{item.media &&
item.media.type === 'image' &&
item.media.data.map((media, mediaIndex) => (
<View key={mediaIndex} style={styles.stepImageContainer}>
<ImageZoom
uri={media?.original}
minScale={0.5}
maxScale={5}
// doubleTapScale={3}
// minPanPointers={1}
// isSingleTapEnabled
// isDoubleTapEnabled
resizeMode="contain"
/>
{/* {imageLoading[media.medium] && (
<ActivityIndicator
style={styles.imageLoader}
size="small"
color="#187293"
/>
)} */}
</View>
))}
{item.lines.map((line, lineIndex) => (
...............
))}
</View>
);
return (
<SafeAreaView style={{flex: 1}}>
<FlatList
showsVerticalScrollIndicator={true}
ListHeaderComponent={() => (
<View style={styles.container}>
<View style={styles.header}>
{guide.image && (
<View>
<Image
source={{uri: guide.image?.medium}}
style={styles.guideImage}
/>
</View>
)}
<View style={styles.headerText}>
............
</View>
<View
style={{
flexDirection: 'row',
alignContent: 'center',
justifyContent: 'space-between',
}}>
<Text style={styles.sectionTitle}>Introduction</Text>
<View style={{flexDirection: 'row'}}>
<TouchableOpacity onPress={addItToFavorites}>
<Image
style={{
width: rh(0.035),
height: rh(0.035),
resizeMode: 'cover',
}}
source={
favoriteIcon
? require('../assets/fullHeart.png')
: require('../assets/emptyHeart.png')
}
/>
</TouchableOpacity>
<TouchableOpacity
onPress={shareApp}
style={{marginLeft: rw(0.05), marginRight: rw(0.02)}}>
<Image
style={{
width: rh(0.033),
height: rh(0.033),
resizeMode: 'cover',
}}
source={require('../assets/share.png')}
/>
</TouchableOpacity>
</View>
</View>
.............
data={guide.tools}
renderItem={renderToolItem}
keyExtractor={(item, index) => index.toString()}
ListFooterComponent={() =>
guide.steps.length > 0 && (
<FlatList
data={guide.steps}
renderItem={renderStepItem}
keyExtractor={item => item.stepid.toString()}
/>
)
}
/>
</SafeAreaView>
);
};
</code>
import React, {useEffect, useLayoutEffect, useState, useCallback} from 'react';
import axios from 'axios';
import {
FlatList,
Image,
Text,
View,
StyleSheet,
ActivityIndicator,
SafeAreaView,
TouchableOpacity,
Share,
Alert,
Linking,
} from 'react-native';
import {useNavigation, useRoute} from '@react-navigation/native';
import {rh, rw} from '../Services/Dimension';
import AppLink from 'react-native-app-link';
import {ImageZoom} from '@likashefqet/react-native-image-zoom';
import AsyncStorage from '@react-native-async-storage/async-storage';
const StepsScreen = () => {
const navigation = useNavigation();
const route = useRoute();
const [guide, setGuide] = useState(null);
const [loading, setLoading] = useState(true);
// const [imageLoading, setImageLoading] = useState({});
const [favoriteIcon, setFavoriteIcon] = useState(false);
const {guideid} = route?.params;
const checkIfFavorite = async () => {
try {
const favoritesString = await AsyncStorage.getItem('favoriteGuides');
const favorites = favoritesString ? JSON.parse(favoritesString) : [];
const isFavorite = favorites.some(fav => fav.guideid === guideid);
setFavoriteIcon(isFavorite);
} catch (error) {
console.log('Error checking favorite guide in AsyncStorage:', error);
}
};
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(
`................`,
);
setGuide(response.data);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
};
fetchData();
checkIfFavorite();
}, [guideid]);
useLayoutEffect(() => {
if (guide?.title) {
navigation.setOptions({
headerTitle:
guide.title.length > 20
? guide.title.slice(0, 19) + '...'
: guide.title,
});
}
}, [guide?.title, navigation]);
if (loading) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#187293" />
</View>
);
}
if (!guide) {
return <Text style={styles.errorText}>No guides available</Text>;
}
const formattedPublishedDate = new Date(
guide.published_date * 1000,
).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
/*
addItToFavorites
*/
const addItToFavorites = async () => {
try {
const favoritesString = await AsyncStorage.getItem('favoriteGuides');
const favorites = favoritesString ? JSON.parse(favoritesString) : [];
const isFavorite = favorites.some(fav => fav.guideid === guideid);
if (isFavorite) {
const updatedFavorites = favorites.filter(
fav => fav.guideid !== guideid,
);
await AsyncStorage.setItem(
'favoriteGuides',
JSON.stringify(updatedFavorites),
);
setFavoriteIcon(false);
} else {
const newFavorite = {guideid, guideTitle: guide.title};
const updatedFavorites = [...favorites, newFavorite];
await AsyncStorage.setItem(
'favoriteGuides',
JSON.stringify(updatedFavorites),
);
setFavoriteIcon(true);
}
} catch (error) {
console.log('Error toggling favorite guide in AsyncStorage:', error);
}
};
const renderToolItem = ({item}) => (
<View style={styles.tableRow}>
........
</View>
);
const renderStepItem = ({item, index}) => (
<View style={styles.stepContainer}>
<View style={styles.stepHeader}>
<Text style={styles.stepNumber}>Step {index + 1}</Text>
<Text
style={{
fontSize: rh(0.02),
color: '#666',
paddingVertical: rh(0.01),
width: rw(0.8),
}}>
{item.title}
</Text>
</View>
{item.media &&
item.media.type === 'image' &&
item.media.data.map((media, mediaIndex) => (
<View key={mediaIndex} style={styles.stepImageContainer}>
<ImageZoom
uri={media?.original}
minScale={0.5}
maxScale={5}
// doubleTapScale={3}
// minPanPointers={1}
// isSingleTapEnabled
// isDoubleTapEnabled
resizeMode="contain"
/>
{/* {imageLoading[media.medium] && (
<ActivityIndicator
style={styles.imageLoader}
size="small"
color="#187293"
/>
)} */}
</View>
))}
{item.lines.map((line, lineIndex) => (
...............
))}
</View>
);
return (
<SafeAreaView style={{flex: 1}}>
<FlatList
showsVerticalScrollIndicator={true}
ListHeaderComponent={() => (
<View style={styles.container}>
<View style={styles.header}>
{guide.image && (
<View>
<Image
source={{uri: guide.image?.medium}}
style={styles.guideImage}
/>
</View>
)}
<View style={styles.headerText}>
............
</View>
<View
style={{
flexDirection: 'row',
alignContent: 'center',
justifyContent: 'space-between',
}}>
<Text style={styles.sectionTitle}>Introduction</Text>
<View style={{flexDirection: 'row'}}>
<TouchableOpacity onPress={addItToFavorites}>
<Image
style={{
width: rh(0.035),
height: rh(0.035),
resizeMode: 'cover',
}}
source={
favoriteIcon
? require('../assets/fullHeart.png')
: require('../assets/emptyHeart.png')
}
/>
</TouchableOpacity>
<TouchableOpacity
onPress={shareApp}
style={{marginLeft: rw(0.05), marginRight: rw(0.02)}}>
<Image
style={{
width: rh(0.033),
height: rh(0.033),
resizeMode: 'cover',
}}
source={require('../assets/share.png')}
/>
</TouchableOpacity>
</View>
</View>
.............
data={guide.tools}
renderItem={renderToolItem}
keyExtractor={(item, index) => index.toString()}
ListFooterComponent={() =>
guide.steps.length > 0 && (
<FlatList
data={guide.steps}
renderItem={renderStepItem}
keyExtractor={item => item.stepid.toString()}
/>
)
}
/>
</SafeAreaView>
);
};