How to re-render a flatlist - react-native

I'm making a mobile app that shows a list of movies, but when I search for a movie FlatList won't update, how can I fix it?
I tried too many things but it still does not work, my objective is to update the list when the button is pressed, the API gives me the data correctly but the list does not update.
This is my code:
export const Home = () => {
let { peliculasList, loadPeliculas } = peliculasPaginated();
const [name, setName] = useState('');
const [year, setYear] = useState('');
const [buscado, setBuscado] = useState(false);
const handleClick = async () => {
const resp = await peliculasApi.get<SimplePelicula[]>(`http://www.omdbapi.com/?t=${name}&y=${year}&plot=full&apikey=d713e8aa`);
setBuscado(!buscado);
peliculasList = resp.data
}
return (
<>
<View
style={{
alignItems: 'center',
height: 760
}}
>
<Text style={{
...style.title,
...style.globalMargin,
top: 0,
marginBottom: 0
}}>Movies</Text>
<TextInput
placeholder='Movie Name'
style={styles.input}
onChangeText={(val) => setName(val)}
/>
<TextInput
placeholder='Year'
style={styles.inputMovie}
onChangeText={(val) => setYear(val)}
/>
<TouchableOpacity onPress={() => handleClick()}>
<ButtonSr></ButtonSr>
</TouchableOpacity>
<FlatList
data={ peliculasList }
keyExtractor={ (pelicula) => pelicula.imdbID }
showsVerticalScrollIndicator={ false }
extraData={ buscado }
renderItem={({ item }) => ( <PeliculasCard pelicula={item} ></PeliculasCard> )}
/>
</View>
</>
)
}

Try to save your resp.data within the state and declare that state in your Flatlist's data prop may solve the issue.

Try this out just change the 'MOVIENAME' to a response from the api such as the movie name and refrence it as the item.API object of your choice
import React, { useState } from 'react'
import { View, Text, TextInput, FlatList, Button } from 'react-native'
export default function App() {
const [FetchedData, setFetchedData] = useState([])
const [SearchTerm, setSearchTerm] = useState('')
const [Data, setData] = useState(FetchedData)
const [ArrayHolder, setArrayHolder] = useState(FetchedData)
const FetchMovies = () => {
fetch('url')
.then(res => res.json())
.then(res => setFetchedData(res))
}
FetchMovies()
function dataSearch(text) {
const newData = ArrayHolder.filter(item => {
const itemData = item.MOVIENAME.toUpperCase()
const textData = text.toUpperCase()
return itemData.indexOf(textData) > -1
})
setData(newData)
}
return (
<View>
<Button title='Press' onPress={() => dataSearch(SearchTerm)} />
<TextInput
onChangeText={(text) => setSearchTerm(text)}
placeholder="Search Here"
/>
<FlatList
data={Data}
renderItem={({ item }) => <Text>{item.MOVIENAME}</Text>}
/>
</View>
)
}

Related

React Native - Render FlatList using API data

I'm trying to get data from an external API and then render it into a flatlist.
I'm very new to React Native so this may be easy to solve.
I'm trying to use the following data: https://www.nationaltrust.org.uk/search/data/all-places
I want to fetch it from the URL, and render the 'title' and 'imageUrl' fields into a flatlist component.
This is what I have so far:
const placesURL = "https://www.nationaltrust.org.uk/search/data/all-places";
const [isLoading, setLoading] = useState(true);
const [places, setPlaces] = useState([]);
useEffect(() => {
fetch(placesURL)
.then((response) => response.json())
.then((json) => setPlaces(json))
.catch((error) => alert(error))
.finally(setLoading(false));
})
And in the flatlist:
export default function App() {
return (
<View style={styles.container}>
<FlatList
data={places}
renderItem={({ item }) => (
<Text>{item.title}</Text>
)}
keyExtractor={(item) => item.id}
/>
<StatusBar style="auto" />
</View>
);
}
If anyone could tell me what to do I would really appreciate it.
try updating your useEffect hook to this
useEffect(() => {
if(places.length === 0 && isLoading){
fetch(placesURL)
.then((response) => response.json())
.then((json) => setPlaces(json))
.catch((error) => alert(error))
.finally(setLoading(false));
}
}, [places, isLoading])
and
export default function App() {
return (
<View style={styles.container}>
{places.length !== 0 &&
<FlatList
data={places}
renderItem={({ item }) => (
<Text>{item.title}</Text>
)}
keyExtractor={(item) => item.id}
/>
}
<StatusBar style="auto" />
</View>
);
}
This URL https://www.nationaltrust.org.uk/search/data/all-places returns a JSON object not an array of objects. It's required to transform an object into an array of objects to be compatible with FlatList.
import React, { useState, useEffect } from "react";
import { Text, View, StyleSheet, FlatList } from "react-native";
const placesURL = "https://www.nationaltrust.org.uk/search/data/all-places";
export default function App() {
const [isLoading, setLoading] = useState(true);
const [places, setPlaces] = useState([]);
const getPlaces = async () => {
try {
const response = await fetch(placesURL);
const result = await response.json();
const newPlaces = Object.values(result);
setPlaces(newPlaces);
setLoading(false);
} catch (error) {
setLoading(false);
console.log(error);
}
};
useEffect(() => {
getPlaces();
}, []);
if (isLoading) {
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text> Searching places.... </Text>
</View>
);
}
return (
<View style={styles.container}>
<FlatList
data={places}
renderItem={({ item }) => <Text>{item.title}</Text>}
keyExtractor={(item) => item.id}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
padding: 20,
},
});
Here is Expo Snack for testing - https://snack.expo.dev/#emmbyiringiro/a98de6
Note: Use Android or iOS emulator, not Web preview.

Change state of RenderItem on screenLeave

Does anyone know how I can change the state of a renderItem when it leaves screen? Below I have the Flatlist with uses an Item, I want to change the state of the item once it exits the renderview.
const Item = memo(({content}) => {
const [outOfView, setOutOfView] = useState(false)
const onScroll= () => {
if (!outOfView) setOutOfView(true) //Trying to get this to work
}
return (
<View style={styles.item} onScroll={onScroll}>
<Text>{content.title}</Text>
</View>
)
})
const Slider = props => {
const flatList = useRef()
const _renderItem = ({ item, index }) => <Item content={item} />
return (
<View style={styles.container} >
{props.header ? <AppText style={styles.header} text={props.header} /> : null}
<FlatList
data={props.data}
horizontal
pagingEnabled
renderItem={_renderItem}
keyExtractor={item => item._id}
ref={flatList}
/>
</View>
)
}
YOu can do something like this
import { useIsFocused } from '#react-navigation/native';
const Item = memo(({content}) => {
const [outOfView, setOutOfView] = useState(false)
const onScroll= () => {
if (!outOfView) setOutOfView(true) //Trying to get this to work
}
const isFocused = useIsFocused();
return (
<View style={styles.item} onScroll={onScroll}>
<Text>{isFocused?content.title:"Offline"}</Text>
</View>
)
})
hope it helps. feel free for doubts

Show ActivityIndicator in React Native?

I have a function to fetch items from an API that is inside UseEffect. And i'm looking to call this function every time the status of the selectedItem or the items changes and show an ActivityIndicator before the function returns the result. The ActivityIndicator appears when the items are uploading but not when the status of the selectedItem changes ?
I have my code like this :
export default () => {
const [items, setItems] = useState();
const [selectedItem, setSelectedItem] = useState(null);
const [isLoading, setLoading] = useState(true);
const getItems = () => {
get('/api/items').then((rep) => {
setItems(rep);
setLoading(false);
}
});
};
useEffect(() => {
getItems();
}, [selectedItem.status]);
return (
<SafeAreaView style={styles.container}>
{isLoading ? (
<View style={[styles.spinnerContainer, styles.horizontal]}>
<ActivityIndicator />
</View>
) : ((items !== [])
&& (
<SectionList
stickySectionHeadersEnabled={false}
style={{ paddingHorizontal: 20, }}
sections={items}
refreshing={isLoading}
keyExtractor={(item, index) => item + index}
...
/>
))}
</SafeAreaView>
);
};
You can try setLoading(true) inside getItems
const getItems = () => {
setLoading(true);
get('/api/items').then((rep) => {
setItems(rep);
setLoading(false);
});
};

Why can't I navigate to my Screen using onPress={() => navigation.navigate('SaveProfileImage, { profileImage })}?

I'm developing an app where I have a screen called Add, other called Save, that I navigate between them using onPress={() => navigation.navigate('Save', { image })} inside a button, so I tried to do the same on another screen called profile that should navigate to the screen SaveProfileImage, but when I try this, I get the error TypeError: undefined is not an object (evaluating 'props.route.params') and I can't understand what can be so wrong in my codes:
Here is the Profile codes:
import React, { useState, useEffect } from 'react';
import { StyleSheet, View, Text, Image, FlatList, Button } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import firebase from 'firebase';
require('firebase/firestore');
import { connect } from 'react-redux';
function Profile({ navigation }, props) {
const [userPosts, setUserPosts] = useState([]);
const [user, setUser] = useState(null);
const [hasGalleryPermission, setHasGalleryPermission] = useState(null);
const [image, setImage] = useState(null);
useEffect(() => {
const { currentUser, posts } = props;
if (props.route.params.uid === firebase.auth().currentUser.uid) {
setUser(currentUser)
setUserPosts(posts)
}
else {
firebase.firestore()
.collection('users')
.doc(props.route.params.uid)
.get()
.then((snapshot) => {
if (snapshot.exists) {
setUser(snapshot.data());
}
else {
console.log('does not exist')
}
})
firebase.firestore()
.collection('posts')
.doc(props.route.params.uid)
.collection('userPosts')
.orderBy('creation', 'desc')
.get()
.then((snapshot) => {
let posts = snapshot.docs.map(doc => {
const data = doc.data();
const id = doc.id;
return { id, ...data }
})
setUserPosts(posts)
});
};
(async () => {
const galleryStatus = await ImagePicker.requestMediaLibraryPermissionsAsync();
setHasGalleryPermission(galleryStatus.status === 'granted');
})();
}, [props.route.params.uid, props.following]);
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 1,
});
if (!result.cancelled) {
setImage(result.uri);
};
};
if (hasGalleryPermission === false) {
return <View />;
};
if (hasGalleryPermission === false) {
return <Text>No access to gallery</Text>;
};
const onLogout = () => {
firebase.auth().signOut();
};
if (user === null) {
return <View />
}
return (
<View style={styles.container}>
{image && <Image source={{ uri: image }}
style={{ flex: 1 }} />}
<Image source={{ uri: props.route.params.profileImage }} />
<View style={styles.containerInfo}>
<Text>{user.name}</Text>
<Text>{user.state}</Text>
<Text>{user.city}</Text>
<Text>{user.email}</Text>
{props.route.params.uid !== firebase.auth().currentUser.uid}
<Button title='Pick Image From Gallery' onPress={() => pickImage()} />
<Button title='Save'
onPress={() => navigation.navigate('SaveProfileImage',
{ profileImage })} />
</View>
<View style={styles.container}>
<FlatList
numColumns={3}
horizontal={false}
data={userPosts}
renderItem={({ item }) => (
<View
style={styles.containerImage}>
<Image
style={styles.image}
source={{ uri: item.downloadURL }}
/>
</View>
)}
/>
</View>
<Button
title='Logout'
onPress={() => onLogout()}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
containerInfo: {
margin: 20
},
containerImage: {
flex: 1 / 3
},
image: {
flex: 1,
aspectRatio: 1 / 1
}
})
const mapStateToProps = (store) => ({
currentUser: store.userState.currentUser,
posts: store.userState.posts,
following: store.userState.following
})
export default connect(mapStateToProps, null)(Profile);
The app.js has a navigation container to control these screens:
<NavigationContainer >
<Stack.Navigator initialRouteName='Main'>
<Stack.Screen name='Main' component={MainScreen} />
<Stack.Screen name='Add' component={AddScreen}
navigation={this.props.navigation}/>
<Stack.Screen name='Save' component={SaveScreen}
navigation={this.props.navigation}/>
<Stack.Screen name='SaveProfileImage' component={SaveProfileImageScreen}
navigation={this.props.navigation}/>
<Stack.Screen name='Comment' component={CommentScreen}
navigation={this.props.navigation}/> </Stack.Navigator>
<NavigationContainer >
I can't understand why I can navigate from the AddScreen to the Save Screen, but I can't do the same between the profileScreen and the SaveProfileImage.
The save is this one:
import React, { useState } from 'react'
import { View, TextInput, Image, Button } from 'react-native'
import firebase from 'firebase'
require('firebase/firestore')
require('firebase/firebase-storage')
export default function Save(props) {
const [caption, setCaption] = useState('')
const uploadImage = async () => {
const uri = props.route.params.image;
const childPath =
`post/${firebase.auth().currentUser.uid}/${Math.random().toString(36)}`;
const response = await fetch(uri);
const blob = await response.blob();
const task = firebase
.storage()
.ref()
.child(childPath)
.put(blob);
const taskProgress = snapshot => {
console.log(`transferred: ${snapshot.bytesTransferred}`)
};
const taskCompleted = () => {
task.snapshot.ref.getDownloadURL().then((snapshot) => {
savePostData(snapshot);
})
};
const taskError = snapshot => {
console.log(snapshot)
};
task.on('state_changed', taskProgress, taskError, taskCompleted);
};
const savePostData = (downloadURL) => {
firebase.firestore()
.collection('posts')
.doc(firebase.auth().currentUser.uid)
.collection('userPosts')
.add({
downloadURL,
caption,
likesCount: 0,
estate: '',
city: '',
creation: firebase.firestore.FieldValue.serverTimestamp()
}).then((function () {
props.navigation.popToTop()
}))
};
return (
<View style={{ flex: 1 }}>
<Image source={{ uri: props.route.params.image }} />
<TextInput
placeholder='Write a Caption . . .'
onChangeText={(caption) => setCaption(caption)}
/>
<Button title='Save' onPress={() => uploadImage()} />
</View>
);
};
The SaveProfileScreen is this other one:
import React, { useState } from 'react'
import { View, TextInput, Image, Button } from 'react-native'
import firebase from 'firebase'
require('firebase/firestore')
require('firebase/firebase-storage')
export default function ProfileImage(props) {
const [caption, setCaption] = useState('')
const uploadImage = async () => {
const uri = props.route.params.image;
const childPath =
`profileImage/${firebase.auth().currentUser.uid}/
${Math.random().toString(36)}`;
console.log(childPath);
const response = await fetch(uri);
const blob = await response.blob();
const task = firebase
.storage()
.ref()
.child(childPath)
.put(blob);
const taskProgress = snapshot => {
console.log(`transferred: ${snapshot.bytesTransferred}`)
};
const taskError = snapshot => {
console.log(snapshot)
};
const taskCompleted = () => {
task.snapshot.ref.getDownloadURL().then((snapshot) => {
savePictureData(snapshot);
})
};
task.on('state_changed', taskProgress, taskError, taskCompleted);
};
return (
<View style={{ flex: 1 }}>
<Image source={{ uri: props.route.params.profileImage }} />
<Button title='Save' onPress={() => uploadImage()} />
</View>
);
};

how do i pass the data to the details screen? Am trying but it is giving me this error : invalid attempt to destructure non-iterable instance

Home Screen:
import React from 'react';
import {View,Text, Image,StyleSheet,TouchableOpacity} from 'react-native';
import {useNavigation} from '#react-navigation/native';
export default function Books({item}){
const firstUrl= 'https://carp-kenya.herokuapp.com';
var secondUrl= String(item.imageFile) ;
var urls= String (firstUrl+secondUrl);
const navigation = useNavigation();
const{Title,description,File}=item;
return (
<View style={styles.container1}>
<TouchableOpacity onPress={()=>navigation.navigate('DetailsScreen',{file:File,Description: description,Title:Title})} >
<Image style={styles.image} source={{uri: urls}}/>
<View style={styles.view2}>
<Text style={styles.title}> {Title}</Text>
</View>
</TouchableOpacity>
</View>
)
}
Source screen:-
import React,{useEffect,useState,useCallback} from 'react';
import {View, TouchableOpacity, Text, StyleSheet, SafeAreaView, FlatList, ActivityIndicator, Alert, ScrollView} from 'react-native';
import Books from '../resources/bookSource';
import {useNavigation} from '#react-navigation/native'
export default function RegistrationScreen({item}){
const [isLoading, setLoading] = useState(false);
const [user, setUser] = useState([]);
const [cat, setCat] = useState([]);
const [text,setText]=useState("");
const [ref, setRef]= useState([])
const [refreshing, setRefreshing] = useState(false);
const navigation = useNavigation();
const ActionNav =()=>{searchData('action')}
const DramaNav =()=>{searchData('drama')}
const InspirationalNav =()=>{searchData('inspirational')}
const EducationalNav =()=>{searchData('educational')}
useEffect(()=>{
fetchData()
},[])
const fetchData = () => {
setLoading(true);
fetch('https://carp-kenya.herokuapp.com/book_json_format/')
.then((response) => response.json())
.then((json) =>{setUser(json),
(setCat(json)),
(setRef(json)) })
.catch(() => Alert.alert('Something went wrong..', 'There was an error.'))
.finally(() => {
setLoading(false)
});
}
const searchData = (text) => {
const newData = cat.filter((item) => {
const itemData = item.category.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setUser(newData);
setText(text);
}
const onRefresh = useCallback(() => {
setRefreshing(true);
fetch('https://carp-kenya.herokuapp.com/book_json_format/')
.then((response) => response.json())
.then((json) =>setUser(json))
.catch(() => Alert.alert('Something went wrong..', 'There was an error.'))
.finally(() => {
setRefreshing(false)
});
}, []);
return(
<SafeAreaView >
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
<View style={styles.container}>
<TouchableOpacity style={styles.category} onPress={InspirationalNav}>
<Text style={styles.text}>Inspirational</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.category} onPress={EducationalNav}>
<Text style={styles.text}>Educative</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.category} onPress={DramaNav} >
<Text style={styles.text}>Drama</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.category} onPress={ActionNav}>
<Text style={styles.text}>Action</Text>
</TouchableOpacity>
</View>
</ScrollView>
{isLoading ? <ActivityIndicator style={styles.containers}/> :
<FlatList
data={user}
showsHorizontalScrollIndicator={false}
keyExtractor={({ id }, index) => id}
renderItem={( {item }) => (<Books item={item} />)
}
numColumns={3}
onRefresh={()=>onRefresh()}
refreshing={refreshing}
/>
}
</SafeAreaView>
);
Invalid attempt to destructure non-iterable instance
says the instance you are trying to iterate is not iterable. What you should do is checking whether the opt object is iterable and can be accessed in the JSX code.
Check how you are passing params props to Books please check and log it