I'm building a simple image listing app with some extra features, including a download image feature.
I've managed to successfully download the image, but somehow the app keeps downloading the wrong one from my JSON.
Rendering items from a JSON:
renderItem = ({ item }) => {
imageToDownload = item.image;
return (
<View style={{ padding: 15, paddingBottom: 0 }}>
<Card elevation={1}>
<View
style={{
flex: 1,
flexDirection: "row",
flexWrap: "wrap",
alignItems: "flex-start"
}}
>
<View style={{ flex: 1 }}>
<TouchableOpacity
onPress={() => {
imageToDownload = item.image;
this.toggleModal();
this.setState({
webViewurl: item.image
});
}}
onLongPress={() => Linking.openURL(item.image)}
activeColor="blue"
>
<ImageBackground
source={{ uri: item.image }}
style={{ height: 216 }}
>
<IconButton
icon="favorite-border"
size={20}
color="#6200EE"
style={{ alignSelf: "flex-end" }}
onPress={this._savedAlert}
/>
</ImageBackground>
</TouchableOpacity>
</View>
</View>
</Card>
</View>
);
}
The download image function:
downloadImage() {
const fileUri = `${FileSystem.documentDirectory}memebook_meme.png`;
FileSystem
.downloadAsync(imageToDownload, fileUri)
.then(({ uri }) => {
console.log('Finished downloading to ', uri);
})
CameraRoll.saveToCameraRoll(fileUri, 'photo');
}
The download button:
<Button
icon="file-download"
mode="contained"
onPress={this.downloadImage}
style={{ borderRadius: 0, width: "33.4%" }}
/>
UPDATE
My data:
{
"listings": [
{
"title": "pikachu",
"image": "https://3.pik.vn/20182c2771b3-e6fd-4018-ba30-ad1d6a1e93ab.jpg"
},
{
...
}
]
}
You are setting imageToDownload inside the renderItem so it will be overwritten with every call of renderItem and downloadImage is using this global variable. Please consider not using global variables as they lead to problems like this.
Make sure to provide the correct value to the download button so that the correct image will be downloaded!
Insert the download button into the renderItem function and use it.
renderItem = ({ item }) => {
...
<Button
icon="file-download"
mode="contained"
onPress={ (item) => this.downloadImage(item)}
style={{ borderRadius: 0, width: "33.4%" }}
/>
...
downloadImage(item) {
const fileUri = `${FileSystem.documentDirectory}memebook_meme.png`;
FileSystem
.downloadAsync(item.image, fileUri)
.then(({ uri }) => {
console.log('Finished downloading to ', uri);
})
CameraRoll.saveToCameraRoll(fileUri, 'photo');
}
Related
I'm new to React Native. I was building my school project and tried to fetch the data to my components, but instead I got this error. I've searched for this error on Google, but I did not get that much information.
Error 1
export default function Home() {
const [recipeData, setRecipeData] = React.useState([localRestaurants]);
const [city, setCity] = useState("Taipei");
const [activeTab, setActiveTab] = useState('Delivery');
const getRecipeFromYelp = () => {
const yelpUrl =
`https://api.yelp.com/v3/businesses/search?term=restaurants&location=${city}`;
const apiOptions = {
headers: {
Authorization: `Bearer ${YELP_API_KEY}`,
},
};
return fetch(yelpUrl, apiOptions)
.then((res) => res.json())
.then((json) =>
setRecipeData(json.businesses));
};
useEffect(() => {
getRecipeFromYelp();
}, [city, activeTab]);
return (
<SafeAreaView style={{
backgroundColor: "#eee",
flex: 1,
}}>
<View style={{
backgroundColor: 'white',
padding: 15,
}}>
<HeaderTabs activeTab={activeTab} setActiveTab={setActiveTab}/>
<SearchBar cityHandler={setCity} />
</View>
<ScrollView showsVerticalScrollIndicator={false}>
<Categories />
<RecipeItems recipeData={recipeData} />
</ScrollView>
<Divider width={1} />
<BottomTabs />
</SafeAreaView>
);
}
Here's my Home screen:
export default function RecipeItems(props) {
return (
<TouchableOpacity activeOpacity={1} style={{
marginBottom: 30,
}}>
{props.recipeData.map((recipe, index) => (
<View
key={index}
style={{
marginTop: 10,
padding: 15,
backgroundColor: "white",
}}>
<RecipeImage image={recipe.image_url}/>
<RecipeInfo
name={recipe.name}
rating={recipe.rating}
/>
</View>
))}
</TouchableOpacity>
);
}
const RecipeImage = (props) => (
<>
<Image
source={{
uri: props.image,
}}
style={{
width: "100%",
height: 180,
}}
/>
<TouchableOpacity style={{
position: 'absolute',
right: 20,
top: 20,
}}>
<MaterialCommunityIcon name='heart-outline' size={25} color='#fff'/>
</TouchableOpacity>
</>
);
const RecipeInfo = (props) => (
<View style={{
flexDirection: 'row',
justifyContent: "space-between",
alignItems: "center",
marginTop: 10,
}}>
<View>
<Text style={{
fontSize: 15,
fontWeight: 'bold',
}}>{props.name}</Text>
<Text style={{
fontSize: 13,
color: "gray",
}}>30-45 • min</Text>
</View>
<View style={{
backgroundColor: "#eee",
height: 30,
width: 30,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 15,
}}>
<Text>{props.rating}</Text>
</View>
</View>
)
And my component.
And since I skipped this error, I added some new code to filter the API's data and also got this error too.
Error 2
return fetch(yelpUrl, apiOptions)
.then((res) => res.json())
.then((json) =>
setRecipeData(json.businesses.filer((business) =>
business.transactions.includes(activeTab.toLowerCase())
)
)
);
};
You need to debug your steps until you find the error. Whether checking the output of owning the object you are accessing or otherwise.
I advise you to use catch to avoid crashing the application until you find the error. It's certainly filtering an object that doesn't exist in the fetch return.
Add "await" to your fetch and "async" in the top of function.
return await fetch(yelpUrl, apiOptions)
.then((res) => res.json())
.then((json) => {
// Try DEBUG your code to check where are the error in filter...
console.log(json);
console.log(json.businesses.filer((business) => business.transactions.includes(activeTab.toLowerCase())));
return null // Or other return to don't crash your app.
}
).catch((error) => {
console.log('Error catch', error);
return null // Or other return to don't crash your app.
});
In the last function where you are filtering using
json.businesses.filer
This a typo. It's filter instead of filer.
Other than that, can you confirm that you are getting an array in response of the API call?
I would like to know what is the initial value of the recipeData?
If it’s undefined at any point, JavaScript can’t perform a map on it, hence the error.
I tried to capture a video using expo-camera, once recorded a video, as we get a uri of video i used that uri to show a preview,to show preview i used video component of expo - av,
But Video component is showing blank screen. Could anyone suggest a fix?
const takeVideo = async ()=>{
if(cameraRef)
{
setIsRecording(true);
let res = await cameraRef.recordAsync();
console.log(res);
setVideoUri(res.uri);
setVideoModalVisible(true);
setIsRecording(false);
}
}
const VideoPreview = (props)=>{
return (
<Modal visible={videoModalVisible}>
<View style={{ flex: 1, margin: 50, paddingTop: 20, flexDirection: "column", justifyContent: "flex-start", alignItems: "center", backgroundColor: "white" }}>
<Video
source={{ uri: videoUri }}
rate={1.0}
volume={1.0}
isMuted={false}
resizeMode="cover"
shouldPlay = {false}
isLooping = {false}
useNativeControls
style={{width:width*0.75,height:height*0.5}}
/>
<View style={{ flexDirection: "column" }}>
<Button mode="contained" style={{ margin: 5 }} onPress={() => { sendFile(videoUri,"video"); }}>Send</Button>
<Button mode="contained" style={{ margin: 5 }} onPress={() => { setVideoModalVisible(false) }}>Close</Button>
</View>
</View>
</Modal>
);
}
These are the codes that i used.Please some one suggest a fix.
I have 2 columns in flatList and I'm trying to align the header component besides the items itself,
Like this
But I got the "add image" above then the items below it,
I'm trying to solve it by using flexWrap in content container style but since I was using numColumns i got a warning that tells flexWrap not supported and use numColumns instead.
so i don't know how can i solve it, so if anybody can help in this case!
here's a snack
Code snippet
const renderItems = ({ item, index }) => {
return (
<View style={{ flex: 0.5, margin: 4 }}>
<View style={[styles.imgContainer, { borderWidth: 0 }]}>
<Image
style={styles.imgStyle}
source={{
uri:
'https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcR1K8ypPsfNVQU8lVxl1i2_ajismMS_w6FA4Q&usqp=CAU',
}}
/>
</View>
</View>
);
};
const renderHeader = () => (
<TouchableOpacity
// onPress={appeandImgs}
style={styles.imgContainer}>
<Image
style={styles.imgStyle}
source={{
uri: 'https://static.thenounproject.com/png/3322766-200.png',
}}
/>
</TouchableOpacity>
);
const keyExtractor = (item, index) => String(index);
<FlatList
data={[1,2,3]}
style={styles.flatList}
numColumns={2}
renderItem={renderItems}
ListHeaderComponentStyle={{
backgroundColor: '#ff0',
width: ScreenWidht / 2 - 20,
}}
keyExtractor={keyExtractor}
ListHeaderComponent={renderHeader}
columnWrapperStyle={{
backgroundColor: '#f07',
}}
contentContainerStyle={{
flexGrow: 1,
paddingBottom: 12,
paddingTop: 15,
}}
/>
Currently, I am using this logic to render data on the basis of results from a grapqhl query. This works fine:
const contacts = () => {
const { loading, error, data } = useUsersQuery({
variables: {
where: { id: 1 },
},
});
if (data) {
console.log('DATA COMING', data);
const contactName = data.users.nodes[0].userRelations[0].relatedUser.firstName
.concat(' ')
.concat(data.users.nodes[0].userRelations[0].relatedUser.lastName);
return (
<View style={styles.users}>
<View style={styles.item} key={data.users.nodes[0].id}>
<Thumbnail
style={styles.thumbnail}
source={{
uri:
'https://cdn4.iconfinder.com/data/icons/avatars-xmas-giveaway/128/girl_avatar_child_kid-512.png',
}}></Thumbnail>
<Text style={styles.userName}>{contactName}</Text>
</View>
</View>
);
}
};
return (
<SafeAreaView style={{ flex: 1 }}>
<Container style={{ flex: 1, alignItems: 'center' }}>
<Item style={styles.addToWhitelist}>
<Icon name="add" onPress={() => navigation.navigate('AddContact')} />
<Text style={styles.addToContactTitle}>Add contact</Text>
</Item>
<Text onPress={() => navigation.navigate('Home')}>Zurück</Text>
<View style={{ width: moderateScale(350) }}>
<Text>Keine Kontacte</Text>
</View>
{contacts()}
{/* <ContactList data={userData}></ContactList> */}
</Container>
</SafeAreaView>
);
};
However, when I make a separate component :
export const ContactList: React.FunctionComponent<UserProps> = ({
data,
}) => {
console.log('user called');
if (!data) return null;
console.log('DATA COMING', data);
const contactName = data.users.nodes[0].userRelations[0].relatedUser.firstName
.concat(' ')
.concat(data.users.nodes[0].userRelations[0].relatedUser.lastName);
return (
<View style={styles.users}>
<View style={styles.item} key={data.users.nodes[0].id}>
<Thumbnail
style={styles.thumbnail}
source={{
uri:
'https://cdn4.iconfinder.com/data/icons/avatars-xmas-giveaway/128/girl_avatar_child_kid-512.png',
}}></Thumbnail>
<Text style={styles.userName}>{contactName}</Text>
</View>
</View>
);
};
and call it like this:
const [userData, setUserData] = useState<UsersQueryHookResult>('');
const contacts = () => {
console.log('running');
const { loading, error, data } = useUsersQuery({
variables: {
where: { id: 1 },
},
});
if (data) {
setUserData(data);
}
};
return (
<SafeAreaView style={{ flex: 1 }}>
<Container style={{ flex: 1, alignItems: 'center' }}>
<Item style={styles.addToWhitelist}>
<Icon name="add" onPress={() => navigation.navigate('AddContact')} />
<Text style={styles.addToContactTitle}>Add contact</Text>
</Item>
<Text onPress={() => navigation.navigate('Home')}>Zurück</Text>
<View style={{ width: moderateScale(350) }}>
<Text>Keine Kontacte</Text>
</View>
{/* {contacts()} */}
<ContactList data={userData}></ContactList>
</Container>
</SafeAreaView>
);
};
However, this gives me a too many re-renders issue. What am I missing? Probably something basic. I also tried using useEffects but I can't run a graphql query hook inside it. That gives me an invalid hook call error.
It seems your running in an endless recursion.
If you call contacts in you render block it causes a setState (through setUserData) which renders, so contacts is called once again and so on till infinite (or till the error).
I am fetching data from http://retailsolution.pk/api/allhome I want to display the title of the product and then all the child products below it, I am getting this output: Here's my code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
Deals: []
};
}
componentWillMount() {
axios
.get("https://retailsolution.pk/api/allhome")
.then(response => this.setState({ Deals: response.data.Deals }));
}
_renderItem(item) {
return (
<View style={{ width: 100, height: 130 }}>
<Image
style={{ width: 100, height: 100 }}
source={{ uri: item.image }}
/>
<Text numberOfLines={1} style={{ flex: 1 }}>
{" "}
{item.name}
</Text>
</View>
);
}
renderTitle() {
return this.state.Deals.map(deal => (
<Text key={deal.parent.id} style={styles.text}>
{deal.parent.name}
</Text>
));
}
renderImage() {
return this.state.Deals.map(deal => (
<FlatList
key={deal.child.product_id}
style={{ marginTop: 5 }}
horizontal
ItemSeparatorComponent={() => <View style={{ width: 5 }} />}
renderItem={({ item }) => this._renderItem(item)}
data={[deal.child]}
/>
));
}
render() {
console.log(this.state.Deals);
return (
<View style={{ flex: 1, marginLeft: 8, marginRight: 8, marginTop: 10 }}>
{this.renderTitle()}
{this.renderImage()}
</View>
);
}
}
In my case {this.renderTitle()} gets execute first and maps every value from the api to the app and then {this.renderImage()} maps all flatlists to the app.
Is there any way I can run this.renderImage() after every iteration of rhis.renderTitle()?
You will have to do it using nested loop.
Try something like this -
{this.state.Deals.map(deal => {
return (
<div>
<Text key={deal.parent.id} style={styles.text}>
{deal.parent.name}
</Text>
{deal.child.map(item => {
return (
<FlatList
key={item.product_id}
style={{ marginTop: 5 }}
horizontal
ItemSeparatorComponent={() => <View style={{ width: 5 }} />}
renderItem={({ item }) => this._renderItem(item)}
data={[item]}
/>
);
})}}
</div>
);
})}
A way to do it is to call a function after the fetch that will create the section data for the Flatlist with the correct format:
sections = [{title: 'Latest', data:["The products data array"]}, {title: 'second section', data : ["Other products"]}, and so on...]`