I finally managed to read the children of the node I wanted but I got another question, how do I display them on my screen properly. I tried to save the snapshot under setToDoData but I cannot bring it out
My code:
const SubScreen3 = ({route}) => {
const paramKey = route.params.paramKey1
const [todoData, setToDoData] = useState([])
useEffect (() => {
const starCountRef = ref(db, "food/" + paramKey);
onValue(starCountRef, (snapshot) =>{
console.log(snapshot.val().Description);
console.log(snapshot.val().Halal);
console.log(snapshot.val().OH);
console.log(snapshot.val().Location);
console.log(snapshot.key);
setToDoData(snapshot)
})
}, [])
return (
<View style = {styles.container}>
<Text style = {styles.header}>{snapshot.key}</Text>
</View>
)
}
export default SubScreen3
Here is my log from the console:
But when I run it, i get the output of Snapshot cannot be found
How do I solve this?
Update: I changed according to the solution provided but it gives me Error: Objects are not valid as a React child (found: object with keys {Description, Halal, Location, OH}). If you meant to render a collection of children, use an array instead. so i used the same method I did for another page to display multiple stuff but I have another issue which is TypeError: undefined is not a function (near '...todoData.map...')
const SubScreen3 = ({route}) => {
const paramKey = route.params.paramKey1
const [todoData, setToDoData] = useState([])
useEffect (() => {
const starCountRef = ref(db, "food/" + paramKey);
onValue(starCountRef, (snapshot) =>{
console.log(snapshot.val().Description);
console.log(snapshot.val().Halal);
console.log(snapshot.val().OH);
console.log(snapshot.val().Location);
console.log(snapshot.key);
setToDoData(snapshot.val());
})
},)
return (
<View style = {styles.container}>
{
todoData.map((item,index) => {
return(
<View key ={index}>
<Text style = {styles.header}>{item.key}</Text>
<Text style = {styles.header}>{item.val().Description}</Text>
<Text style = {styles.header}>{item.val().Location}</Text>
<Text style = {styles.header}>{item.val().OH}</Text>
<Text style = {styles.header}>{item.val().Halal}</Text>
<TouchableOpacity
//onPress={() => navigation.navigate("SubScreen1", {paramKey:value})}
style = {styles.button}
>
<Text style = {styles.buttonText}>How to go?</Text>
</TouchableOpacity>
<TouchableOpacity
//onPress={}
style = {styles.button}
>
<Text style = {styles.buttonText}>Reviews</Text>
</TouchableOpacity>
</View>
)
})
}
</View>
)
}
export default SubScreen3
Expected screen output: Something like this
What actually happen:
My reading of the DataSnapshot API seems to indicate that you want to call snapshot.val() to extract the data from the snapshot. Personally, I would be storing that data in the state variable rather than the snapshot itself:
setToDoData(snapshot.val());
Then in your JSX, you should be referring to the todoData variable, not the snapshot variable (which does not exist outside of the onValue() callback function.
Also, I would recommend NOT initializing your state variable to the value []. Simply leave the initialization blank, and check for a "falsey value" in your JSX. Otherwise your code will not be able to distinguish between "has not received a value yet" (keep it as undefined) and "received a value but it has no data" (i.e. your query returns []).
ANSWER TO THE EDITED QUESTION:
Your JSX has been updated to now use todoData.map(), meaning that todoData is expected to have an array. But the code we gave in the earlier answer simply puts an object into todoData.
To support your newer JSX, you would modify the code where you put the data into the state variable such that you are appending to an array:
setToDoData(curVal => {
return [...curVal, snapshot.val()]
});
This new code now uses the second form of the "set state function" where you provide a callback function. When the "set state function" runs, it will call the callback function passing the "current value" of the state variable. From that, we expand it into a new array and append the new data from the snapshot.
Please, if there is a new problem, post a new SO post. Editing posts like this is not advisable and becomes very hard to follow after just a couple of iterations.
Related
Hello and thank you for your time in advance!
I am struggling with a small issue that I have not encountered before, with React not rendering an UI element based on a check function. Basically, what I am trying to make, is a multiple selection filter menu, where when an option is clicked, the dot next to it changes to red.
For this purpose I append the value of each option to an array, and using array.sort, verify when it is to be added (value is pushed to FilterList) and removed (value is popped from FilterList)
The checkfilter function operates normally, and when logging the state, indeed it works as intended with the array being updated.
However, the JSX code running the checkfilter to render the extra red dot inside the bigger circle, unfortunately does not.
Additionally, when the screen is refreshed, the UI is updated normally, with every option clicked, now showing the aforementioned red dot.
Why is this happening? I have tried several hooks, JSX approaches, using imported components and more that I can't even remember, yet the UI will not update oddly.
Below you can find a snippet of the code. Please bear in mind this is a render function for a flatlist component
const checkFilter = useCallback((element) => {
return filterList?.some((el: any) => (el == element))
}, [filterList])
const removeFilter = useCallback((cat) => {
let temparr = filterList
var index = temparr?.indexOf(cat);
if (index > -1) {
temparr?.splice(index, 1);
}
setFilterList(temparr)
}, [filterList])
const addFilter = useCallback((cat) => {
let temparr = filterList;
temparr.push(cat);
setFilterList(temparr)
}, [filterList])
const renderFilter = useCallback((item) => {
return (
<Pressable
onPress={() => {
checkFilter(item?.item) ? removeFilter(item?.item) : addFilter(item?.item);
console.log(filterList)
}}
style={styles.modalOptionWrapper}
>
<Text style={styles.modalOptionTitle(checkFilter)}>{item?.item}</Text>
<View style={styles.modalOptionRowRight}>
<View style={styles.radioBtn}>
{checkFilter(item?.item) ?
<View style={styles.radioBtnBullet} />
:
null
}
</View>
</View>
</Pressable>
)
}, [filterList])
This may not be correct answer but try this. When I say simple basic codes, like this.
const ListItems = () => {
const [filterList, setFilterList] = React.useState([]); // must be array
const checkFilter = filterList?.some((el) => el === element);
const removeFilter = useCallback(
(cat) => {
// not updating new state, just useing previous state
setFilterList((prev) => [...prev].filter((el) => el !== cat));
// used spread iterator
},
[] // no need dependency
);
const addFilter = useCallback((cat) => {
// used spread indicator
setFilterList((prev) => [...prev, cat]);
}, []);
const renderFilter = useCallback((item) => {
// just checking our codes is right with basic elements
// it may be 'undefined'
return <Text>{JSON.stringify(item)}</Text>;
}, []);
return filterList.map(renderFilter);
};
Im trying to add a new data and put it at the top of the array that I got from an API. The problem is that the new data is still at the end of the array and it only renders the new data instead of all of it even though the console.log output all of the data. I know the problem is not the style because it was able to render properly if I didn't add a new data.
This is how I add the new data:
fetch()
**********
.then((result) => {
setListTab(result);
setListTab(listTab => [listTab, {id: 18, name: "All", ...listTab}]);
console.log(listTab);
<ScrollView
showsHorizontalScrollIndicator={false}
horizontal={true}>
{listTab.map(item => (
<TouchableOpacity key={item.id}
activeOpacity={1}>
<Text>{item.name}</Text>
</TouchableOpacity>
))}
First, off you should not use ScrollView to implement Lists, please use FlatList.
Your logic (as per the code you posted) is not right. To add a new object to the first of the array you can very well use the spread operator.
Example code: https://jsfiddle.net/rzhybtdj/
let friendsList = [{"id":343,"name":"Satheesh"}, {"id":342,"name":"Kavitha"}];
// something happened and I have a new friend now.
// To add it to the firendsList array I do this
const newFriend = {"id":341,"name":"Yuvan"};
friendsList = [newFriend, ...friendsList];
Now come to your code and how it should be to make what you want.
fetch()
.then((result) => {
const newList = [...result, ...listTab]; //considering result being an array, if an object then [result, ...listTab]
setListTab(newList);
console.log(newList);
Let me know if this works for you.
renderDuration(x) {
return 'abc'
}
render() {
return (
<View>
{this.props.list.map(x => (
<Text>{this.renderDuration(x)}</Text>
))}
</View>
)
}
The above code is working perfectly fine. The situation is very basic which is looping the list and for each of the element, call the method renderDuration and get the individual string value. Now take a look below.
async renderDuration(x) {
let someAsyncOpt = await getTextFromSomewhere();
return someAsyncOpt;
}
So once the same method we change it to async method, it breaks and hitting exception
Invariant Violation: Invariant Violation: Objects are not valid as a React child (found: object with keys {_40, _65, _55, _72}). If you meant to render a collection of children, use an array instead.
I understand that a viable option is to get whatever data that's needed first, instead while render. This question is basically trying to explore the possibility to perform async operation while mapping, if it make sense?
UPDATES:
I've included the below code to show that it has nothing to do with wrong type of returning from async opt. It's basically the moment we include the keyword async, it will break
async renderDuration(x) {
return 'abc';
}
(Update) try to use this:
class Alpha{
// ...
// Update :
async renderDuration(x) {
let someAsyncOpt = await Promise.all(getTextFromSomewhere());
return someAsyncOpt;
}
render() {
return (
// Old :
<View>
{this.props.list.map(x => (
<Text>{this.renderDuration(x)}</Text>
))}
</View>
// Update :
<View>
{
this.props.list.map(
async (x) => { await this.renderDuration(x) }
);
}
</View>
)
}
}
This question already has answers here:
array.length is zero, but the array has elements in it [duplicate]
(2 answers)
Closed 4 years ago.
So I did an interesting test because my data is not populating in my FlatList React Native element. I made two state variables: realList and fakeList:
state = {
realList: [],
fakeList: [],
}
Then in componentWillMount, the following function is run which populates two arrays called real and fake. One with data pulled from Firebase, the other hardcoded with the SAME array information:
listenForMusic = () => {
var dataRef = database.ref("music");
let real = [];
let fake = [];
dataRef.orderByChild("date").on('child_added', (snap) => {
var url = snap.val().youtubeURL;
var vidTitle = snap.val().title;
var thumb = snap.val().thumbnail;
real.push({
videoURL: url,
title: vidTitle,
thumbnail: thumb
});
});
fake.push({videoURL: "https://youtu.be/AHukwv_VX9A", title: "MISSIO - Everybody Gets High (Audio)", thumbnail: "https://i.ytimg.com/vi/AHukwv_VX9A/hqdefault.jpg"}, {videoURL: "https://youtu.be/G-yWpz0xkWY", title: "SMNM - Million ft. Compulsive", thumbnail: "https://i.ytimg.com/vi/G-yWpz0xkWY/hqdefault.jpg"});
this.setState({
realList: real,
fakeList: fake
});
}
Then I console.log both of the arrays after the render function:
render() {
console.log("Actual", this.state.realList);
console.log("Fake", this.state.fakeList);
return (
<View>
<FlatList
data={this.state.fakeList}
renderItem={({item}) => <Text>{item.videoURL}</Text>}
/>
</View>
);
}
and I see this:
And opening both:
So my question is, why does the "real" array look empty but still has data populated inside while the "fake" array displays that it holds two objects inside of it, even before we take a look inside??
In my FlatList, if I use my fakeList of data, I can display the data on screen, but if I use my realList, nothing shows up on my screen.
EDIT: Added full code for reference:
class VideoFeed extends React.Component {
state = {
itemList: [],
}
componentDidMount() {
this.listenForMusic();
}
listenForMusic = () => {
var dataRef = database.ref("music");
let items = [];
dataRef.orderByChild("date").on('child_added', (snap) => {
items.push({
videoURL: snap.val().youtubeURL,
title: snap.val().title,
thumbnail: snap.val().thumbnail
});
});
this.setState({ itemList: items })
}
_renderVideoItem = ({item}) => (
<TouchableWithoutFeedback
onPress={Actions.Submit}
>
<View style={styles.mediaContainer}>
<Image
source={{uri: item.thumbnail }}
style={styles.mediaThumbnail}
/>
<View style={styles.mediaMetaContainer}>
<View style={styles.topMetaContainer}>
<Text style={styles.mediaTitle}>
{item.title}
</Text>
<Text style={styles.sharedByUser}>
UNCVRD
</Text>
</View>
<View style={styles.bottomMetaContainer}>
<Icon
name='youtube-play'
type='material-community'
color='#ff0000'
size={16}
/>
<View style={styles.bottomRightContainer}>
<Icon
name='thumb-up'
size={12}
color='#aaa'
/>
<Text style={styles.metaLikeCounter}>
16
</Text>
</View>
</View>
</View>
</View>
</TouchableWithoutFeedback>
);
render() {
console.log(this.state.itemList); // this list is populated
return (
<View>
<FlatList
data={this.state.itemList}
renderItem={({item}) => {
console.log('item in render --> ', item); return (<Text>{item.videoURL}</Text>) }}
/>
</View>
);
}
}
The console window holds object references. The real is empty when the first console.log statement is executed. By the time you expand it. The events have fired. Your array is no longer empty. You can test this in the console:
let real = [];
let fake = [1, 2];
console.log(real, fake);
setTimeout(() => real.push(9))
When you inspect an object from a console log in more detail, your console will do a new lookup for that object. So what happens in your case is that at the point of your console log the array is empty, but gets populated shortly after. When you inspect it, you will see the object as it exists in memory at the time of inspection.
It is not advisable to do async stuff in componentWillMount (you will miss out on a re-render), better move that over to componentDidMount.
Edit:
Since you want your component to automatically update when your Firebase data changes, the best way to go is to make use of a property in your React class which holds the reference to your Firebase data. More information on exactly how to do this you can find here. Check the section Realtime Database listener.
class VideoFeed extends React.Component {
constructor(props) {
super(props);
this.state = {itemList: []};
this.realList = firebaseApp.database().ref(); // create property
}
componentDidMount() {
this.listenForMusic(this.realList); // pass property to listener method
}
listenForMusic = (list) => { // listener method
let items = [];
list.orderByChild("date").on('child_added', (snap) => {
items.push({
videoURL: snap.val().youtubeURL,
title: snap.val().title,
thumbnail: snap.val().thumbnail
});
this.setState({ itemList: items }); // update state
});
}
// rest of component code ...
}
I want to see the link to my image in the imageUrl variable.
When I log sampleImage, I see JSON-like data and I can see a link in this data's i section.
But when I log imageUrl, it returns undefined.
render() {
const uid = '12345';
const imageRef = firebase.storage().ref(uid).child(uid);
const sampleImage = imageRef.getDownloadURL().then();
const imageUrl = sampleImage.i;
console.log(sampleImage);
console.log(imageUrl);
return (
<View>
<Image style={{ width: 55, height: 55 }} source={{ uri: null }} />
</View>
);
}
}
Getting an image is a network request so it takes a little while. If you want to log the image (and you don't use async/await) then you have to do it inside the .then()
const sampleImage = imageRef.getDownloadURL().then(result => console.log(result));
Rather than doing all of this in the render method, do it when the component mounts and store the URL in state. That way you can just reference the state object in your render method.