Render component from array values in React Native - arrays

I'm trying to render component/function from array values.
Main function
const GeneratedHistory = () => {
return (
<View style={styles.container}>
<View style={styles.headerWrapper}>
<Text variant="headlineLarge" style={styles.headersText}>Historia</Text>
<Text variant='labelMedium'>Generowane kody</Text>
</View>
<View style={styles.mainWrapper}>
<ScrollView>
{getItems()}
</ScrollView>
</View>
</View>
I retrieving values from Firestore and saves what i want to array named Items.
function getItems() {
const items = [];
try {
firebase.firestore().collection("Generated").where("username", "==", auth.currentUser.email)
.get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
items.push({
qrImage: doc.get("qrImage"),
qrText: doc.get("qrText"),
time: doc.get("time"),
})
});
items.map((item) => {
console.log(item.qrText)
})
});
} catch (error) {
alert('Error occured')
}
}
Nextly i map the array, printing to console and trying to render function named SingleElement.
function singleElement(text) {
return (
{text}
)
}
Logging to console work's fine, but i can't render the function.
Screen just stays white.

So, I have to use async function, in my case, I fetch the data when the window opens and save it to array.
useEffect(() => {
async function fetchData() {
todoRef
.onSnapshot(
querySnaphsot => {
const items = []
querySnaphsot.forEach((doc) => {
const { qrImage, qrText, time } = doc.data()
items.push({
id: doc.id,
qrImage,
qrText,
time,
})
setItems(items);
})
}
)
} fetchData()
}, [])
Then I map the elements and display them in the component.
items.map((item) => {
return <YourComponent key={item.id} text={item.qrText} time={item.time}>
</YourComponent>
})
}

Related

Objects are not valid as a React child, use an array instead

I am trying to render the first and last name from a json request using axios.
I am getting the following error you see in the title. I have included a snack example here reproducing the error exactly as well as added the code below.
Thank you
const plsWork = () => {
// Make a request for a user with a given ID
return axios.get('https://randomuser.me/api')
.then(({data}) => {
console.log(data);
return data
})
.catch(err => {
console.error(err);
});
}
const userName = (userInfo) => {
const {name: {first, last}} = userInfo;
return {first}, {last};
}
export default function App() {
const [data, setData] = React.useState(' ')
const [userInfos, setUserInfos] = React.useState([]);
React.useEffect(() => {
plsWork().then(randomData => {
setData(JSON.stringify(randomData, null, 4) || 'No user data found.')
setUserInfos(randomData.results)
})
}, []);
return (
<View>
<ScrollView>
{
userInfos.map((userInfo, idx) => (
<Text key={idx}>
{userName(userInfo)}
</Text>
))
}
<Text style={{color: 'black', fontSize: 15}}>
{data}
</Text>
</ScrollView>
</View>
);
}
You have to return a React Component in the userName function.
In the line 21:
Change from return {first}, {last} to return <>{first}, {last}</>.
It should work!
Here is code edited: snack expo

How to create automatic Item Divider for React Native FlatLists?

I have a list of events that I am rendering in a FlatList. I would like there to be a divider whenever the event is on a different date - aka when {item.eventID.eventDate} for a given item is different to the one before it (I already know how to call the sever to return the dates in order).
Is there a way to autogenerate these dividers?
Here is my function for each item of the FlatList:
function Item({ item }) {
return (
<View>
<Text>{item.eventID.eventDate}</Text>
<Text>{item.eventID.artistName}</Text>
<Text>{item.ticketID}</Text>
</View>
);
}
And here is my class component for the page:
export default class MyEventsScreen extends Component {
state = {
tickets: [],
};
componentDidMount = () => {
fetch("http://127.0.0.1:8000/api/fullticket/", {
method: "GET",
})
.then((response) => response.json())
.then((responseJson) => {
this.setState({
tickets: responseJson,
});
})
.catch((error) => {
console.error(error);
});
};
render() {
return (
<View>
<FlatList
style={{ flex: 1 }}
data={this.state.tickets}
renderItem={({ item }) => <Item item={item} />}
keyExtractor={(item) => item.ticketID}
/>
</View>
);
}
}
You can write a custom function to render divider when the date value is changed.
The new Item function and renderDivider:
let prevDate = ""
function renderDivider(date) {
if(prevDate === "" || date !== prevDate) {
prevDate = date //initialize prevDate
return <Text style={styles.divider}>----{date}---</Text>
}
}
function Item({ item }) {
return (
<View>
{renderDivider(item.eventID.eventDate)}
<Text>{item.eventID.artistName}</Text>
</View>
);
}
const styles = StyleSheet.create({
divider: {
marginVertical: 15,
fontWeight: '700',
color: 'rgb(100,100,100)'
}
});

React Native Deck Swiper

I am trying to make a GET request to an enpoint using the two functional components below to display in my react native deck swipper
//using fetch
const getDataUsingFetch = () => {
fetch(latestNews+ApiKey)
.then((response) => response.json())
.then((responseJson) => {
// set the state of the output here
console.log(responseJson);
setLatestNews(responseJson);
})
.catch((error) => {
console.error(error);
});
}
//using anxios
//asynchronous get request call to fetech latest news
const getDataUsingAnxios = async () => {
//show loading
setLoading(true);
setTimeout(async () => {
//hide loading after the data has been fetched
setLoading(false);
try {
const response = await axios.get(latestNews+ApiKey);
setLatestNews(response.data);
setLoading(false);
console.log(getLatestNews);
} catch (error) {
// handle error
alert(error.message);
}
}, 5000);
};
Data returned when logged from console:
Array [
Object {
"category_id": "8",
"content": "Hi",
"created_at": "2020-11-12T12:43:03.000000Z",
"featured_image": "splash-background_1605184983.jpg",
"id": 19,
"news_url": "doerlife.com",
"title": "I m good how about you",
"updated_at": "2020-11-12T12:43:03.000000Z",
}....]
I now save the data into a state array
const [getLatestNews, setLatestNews] = useState([]);
Here is my swipper(some code ommited - not necessary)
<Swiper
ref={useSwiper}
//cards={categoryID(docs, "2")}
cards={getLatestNews}
cardIndex={0}
backgroundColor="transparent"
stackSize={2}
showSecondCard
cardHorizontalMargin={0}
animateCardOpacity
disableBottomSwipe
renderCard={(card) => <Card card={card} />}
.....
When I try to access any data in the array from my Card reusable component, e.g card.featured_image
I WILL GET THIS ERROR - TypeError: undefined is not an object (evaluating 'card.featured_image').
PLEASE CAN SOMEONE HELP ME.
//Card reusable component for deck swipper
import React from 'react'
import { View, Text, Image, ImageSourcePropType } from 'react-native'
import styles from './Card.styles'
const Card = ({ card }) => (
<View activeOpacity={1} style={styles.card}>
<Image
style={styles.image}
source={card.featured_image}
resizeMode="cover"
/>
<View style={styles.photoDescriptionContainer}>
<Text style={styles.title}>{`${card.title}`}</Text>
<Text style={styles.content}>{`${card.content}`}</Text>
<Text style={styles.details}>
Swipe Left to read news in details
</Text>
</View>
</View>
);
export default Card
I've done something similar to this before so I think I can help a bit. The problem here is that your getLatestNews state has not been updated yet before the cards render. You can fix the problem by having another state called "isDataReturned". Then, have a useEffect that triggers whenever getLatestNews's length changes. If getLatestNews's length is > 0, then you can set isDataReturned to be true and render the deck only when isDataReturned is true.
Here's a code sample that I made:
const [getLatestNews, setLatestNews] = useState([]);
const [dataIsReturned, setDataIsReturned] = useState(false)
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://cat-fact.herokuapp.com/facts',
);
setLatestNews(result.data);
};
fetchData();
}, []);
useEffect(() => {
if (getLatestNews.length > 0) {
setDataIsReturned(true)
} else {
setDataIsReturned(false)
}
}, [getLatestNews.length])
if( dataIsReturned === true) {
return (
<View style={styles.container}>
<Swiper
cards={getLatestNews}
renderCard={(card) => {
return (
<View style={styles.card}>
<Text>{card.text}</Text>
</View>
)
}}
onSwiped={(cardIndex) => {console.log(cardIndex)}}
onSwipedAll={() => {console.log('onSwipedAll')}}
cardIndex={0}
backgroundColor={'#4FD0E9'}
stackSize= {3}>
</Swiper>
</View>)
} else {
return(<Text>Loading</Text>)
}
In the renderCard attribute, i changed it from
renderCard={(card) => <Cardz card={card} />}
to
renderCard={(card) => (card && <Cardz card={card} />) || null}
and it worked.

AsyncStorage not working

console.log returns undefined.
AsyncStorage doesn't work. It doesn't save and does not register.
Please help me.
if (this.props.zamqiDetay == 1) {
this.setState({ number0State: this.props.processDetayDeger });
AsyncStorage.setItem('loover', 'sadfasd');
}
Render:
render() {
AsyncStorage.getItem('loover').then((value) => {
this.setState({ loover: value });
}).done();
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 20 }}>
<ActivityIndicator />
</View>
);
}
console.log(this.state.loover);
return ();
You can do something like this.
let storage = async () => await AsyncStorage.getItem('item');
storage().then((res)=>{
if(res) {
//we have out data
}
}).catch((err)=>{
// oops
});
You need to declare await keyword while doing setItem and getItem in asynchronous storage
await AsyncStorage.getItem('loover').then((value) => {
this.setState({ loover: value });
}).done();
await AsyncStorage.setItem('loover', 'sadfasd');

React native delete multiple items from state array

I have a directory which stores images taken using the camera. For saving images I am using RNFS. I am using react-native-photo-browser.
The gallery itself doesn't have any options to delete the items from the gallery. So I am working to achieve it
export default class GridGallery extends React.Component{
static navigationOptions = {
title: 'Image Gallery',
};
constructor(props) {
super(props)
this.state = {
filesList : [],
mediaSelected: [],
base64URI: null,
galleryList: []
}
}
componentDidMount(){
FileList.list((files) => {
if(files != null) {
this.fileUrl = files[0].path;
files = files.sort((a, b) => {
if (a.ctime < b.ctime)
return 1;
if (a.ctime > b.ctime)
return -1;
return 0;
});
this.setState({
filesList: files
});
}
console.warn(this.state.filesList);
this.getFiles();
});
}
getFiles(){
//console.warn(this.state.filesList);
const ArrFiles = this.state.filesList.map((file) =>
({ caption : file.name, photo : file.path })
);
//console.warn(ArrFiles);
this.setState({ galleryList : ArrFiles });
}
onActionButton = (media, index) => {
if (Platform.OS === 'ios') {
ActionSheetIOS.showShareActionSheetWithOptions(
{
url: media.photo,
message: media.caption,
},
() => {},
() => {},
);
} else {
alert(`handle sharing on android for ${media.photo}, index: ${index}`);
}
};
handleSelection = async (media, index, isSelected) => {
if (isSelected == true) {
this.state.mediaSelected.push(media.photo);
} else {
this.state.mediaSelected.splice(this.state.mediaSelected.indexOf(media.photo), 1);
}
console.warn(this.state.mediaSelected);
}
deleteImageFile = () => {
const dirPicutures = RNFS.DocumentDirectoryPath;
//delete mulitple files
console.warn(this.state.mediaSelected);
this.state.mediaSelected.map((file) =>
// filepath = `${dirPicutures}/${file}`
RNFS.exists(`${file}`)
.then( (result) => {
console.warn("file exists: ", result);
if(result){
return RNFS.unlink(`${file}`)
.then(() => {
console.warn('FILE DELETED');
let tempgalleryList = this.state.galleryList.filter(item => item.photo !== file);
this.setState({ galleryList : tempgalleryList })
})
// `unlink` will throw an error, if the item to unlink does not exist
.catch((err) => {
console.warn(err.message);
});
}
})
.catch((err) => {
console.warn(err.message);
})
)
}
renderDelete(){
const { galleryList } = this.state;
if(galleryList.length>0){
return(
<View style={styles.topRightContainer}>
<TouchableOpacity style={{alignItems: 'center',right: 10}} onPress={this.deleteImageFile}>
<Image
style={{width: 24, height: 24}}
source={require('../assets/images/ic_delete.png')}
/>
</TouchableOpacity>
</View>
)
}
}
goBack() {
const { navigation } = this.props;
navigation.pop;
}
render() {
const { galleryList } = this.state;
return (
<View style={styles.container}>
<View style={{flex: 1}}>
<PhotoBrowser
mediaList={galleryList}
enableGrid={true}
displayNavArrows={true}
displaySelectionButtons={true}
displayActionButton={true}
onActionButton={this.onActionButton}
displayTopBar = {true}
onSelectionChanged={this.handleSelection}
startOnGrid={true}
initialIndex={0}
/>
</View>
{this.renderDelete()}
</View>
)
}
}
An example list of images:
[
{
photo:'4072710001_f36316ddc7_b.jpg',
caption: 'Grotto of the Madonna',
},
{
photo: /media/broadchurch_thumbnail.png,
caption: 'Broadchurch Scene',
},
{
photo:
'4052876281_6e068ac860_b.jpg',
caption: 'Beautiful Eyes',
},
]
My aim is whenever the item from state galleryList is removed I need to refresh the component, so the deleted image will be removed from the gallery. So When I try to use filter the galleryList it deleting other images instead of other images:
let tempgalleryList = this.state.galleryList.filter(item => item.photo !== file);
this.setState({ galleryList : tempgalleryList })
MCVE -> This is a minified version of my code, you can see the images are deleting randomly
Problem
let tempgalleryList = this.state.galleryList.filter(item => item.photo !== file);
this.setState({ galleryList : tempgalleryList })
Since setState is async, this.state.galleryList will not be updated in each iteration of your map function, so the final updated state will only have one item filtered out instead of all selected items.
Solution
You can use the callback version of setState which uses the updated state instead:
this.setState(prevState => ({
galleryList : prevState.galleryList.filter(item => item.photo !== file),
}));
Alternative solution
Instead of calling setState in every iteration, you can call it outside of your map function instead (though setState updates will be batched anyway so no significant performance improvement):
this.setState(prevState => ({
galleryList : prevState.galleryList.filter(item => !prevState.mediaSelected.includes(item.photo)),
}));
Other problems with your code
this.state.mediaSelected.push(media.photo);
} else {
this.state.mediaSelected.splice(this.state.mediaSelected.indexOf(media.photo), 1);
You are directly mutating your state here. Do this instead:
this.setState(prevState => ({
mediaSelected: prevState.mediaSelected.concat(media.photo)
}));
this.setState(prevState => ({
mediaSelected: prevState.mediaSelected.filter(e => e != media.photo)
}));

Resources