React-Native Firebase Display array of objects using FlatList - javascript

I'm trying to display some Firebase data, but nothing displays.
export default class ListGroupScreen extends Component {
constructor(){
super();
this.state = {
dataArray: [],
}
}
componentDidMount() {
let that = this;
firebase.database().ref('/groups').on('child_added', function (data){
that.setState({
dataArray: data.val()
})
})
}
render() {
console.log(this.state.dataArray);
console.log(this.state.dataArray[0]);
return (
<List>
<FlatList
data={this.state.dataArray}
renderItem={({ item }) => (
<ListItem
title={<Text>{item.groupTitle}</Text>}
time={<Text>{item.groupTime}</Text>}
/>
)}
/>
</List>
);
}
}
The console.log(this.state.dataArray); gives me all the items in the database, but console.log(this.state.dataArray[0]); gives me undefined. as shown here:
This is what the database looks like:

The reason is that .on('child_added') returns a single object for each item in the groups node.
In your case you need to use .once('value'), which will return you a collection (object) with the items, that you have to convert into an array:
firebase.database().ref('/groups').once('value', function(snapshot) {
var returnArray = [];
snapshot.forEach(function(snap) {
var item = snap.val();
item.key = snap.key;
returnArray.push(item);
});
// this.setState({ dataArray: returnArray })
return returnArray;
});

FlatList react native component expects data props to be an array. You are passing it as an Object, even though you declared in contractor as an array; but in componentDidMount you are overriding to object. You can change it to an array of Objects.
console.log(this.state.dataArray[0]) definitely give undefined because it is not an array
export default class ListGroupScreen extends Component {
constructor(){
super();
this.state = {
data: null,
}
}
componentDidMount() {
const that = this;
firebase.database().ref('/groups').on('child_added', function (data){
that.setState({
data: data.val()
})
})
}
render() {
const dataArray = Object.values(this.state.data)
return (
<List>
<FlatList
data={dataArray}
renderItem={({ item }) => (
<ListItem
title={<Text>{item.groupTitle}</Text>}
time={<Text>{item.groupTime}</Text>}
/>
)}
/>
</List>
);
}
}

Related

Passing onChange event from mapped child to parent's state returns 'undefined' error

Hello [from react beginner].
Trying to pass child's input value to parent state.
So, App has an array:
export default class App extends React.Component {
state = {
data: [
{id: 1, name: 'john'},
{id: 2, name: 'doe'},
]
}
render() {
return (
<List data={this.state.data}/>
)
}
}
Then List takes prop.data as state.data and returns children in map:
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
data: this.props.data
};
this.parentChange = this.parentChange.bind(this);
}
renderList() {
const data = this.state.data;
let list = null;
if (data.length) {
list = data.map(function(item, index){
return (<Item key={item.id} data={item} onChange={(e, index) => this.parentChange(e, index)} />)
});
} else {
list = <p>nothing here</p>
}
return list;
}
parentChange(value, index) {
// pls give me anything
console.log('--- value: ', value);
console.log('--- index: ', index);
}
render() {
return (
<div>{this.renderList()}</div>
)
}
}
And Item child:
class Item extends React.Component {
render() {
const {id, name} = this.props.data;
return (
<div>
<input id={id} value={name} onChange={(e) => this.props.onChange(e, id)} />
</div>
)
}
}
But if I change any input's value there is an error as result
Cannot read property 'parentChange' of undefined
Thanks for any help (code, links etc)
You are declaring a function with function keyword:
if (data.length) {
list = data.map(function(item, index){
return (<Item key={item.id} data={item} onChange={(e, index) =>
this.parentChange(e, index)} />)
});
}
Declaring a function with the function keyword will create another context inside itself, so your this (context) will no longer be the class context.
The IDE might not warn you but when it runs, JS create another context inside your function result in an undefined error.
So it will need to change to:
if (data.length) {
list = data.map((item, index) => {
return (<Item key={item.id} data={item} onChange={(e, index) =>
this.parentChange(e, index)} />)
});
}

How to get AsyncStorage data without waiting in react native?

I have trouble trying to retrieve data from AsyncStorage, I can't directly assign a state like that, since it always returns undifined, how can I avoid that?
export default class ListTodo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {},
};
}
componentDidMount() {
//promise
GetDataAsyncStorage('#TODOS').then((data) => {
this.setState({
data: data,
});
});
}
render() {
const {data} = this.state;
console.log(data); // undifined
return (
<>
<Header />
<View>
<FlatList
data={data}
renderItem={({item}) => <TodoItemComponent data={item} />}
keyExtractor={(item) => item.id}
/>
</View>
</>
);
}
}
Here is my function to get data from asynStorage
export const GetDataAsyncStorage = async (key) => {
try {
let data = await AsyncStorage.getItem(key);
return {status: true, data: JSON.parse(data)};
} catch (error) {
return {status: false};
}
};
Add a state variable isLoading and toggle it after the data is got from AsyncStorage
snack: https://snack.expo.io/#ashwith00/async
code:
export default class ListTodo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {},
isLoading: false,
};
}
componentDidMount() {
this.getData();
}
getData = () => {
this.setState({
isLoading: true,
});
//promise
GetDataAsyncStorage('#TODOS').then((data) => {
this.setState({
data: data,
isLoading: false,
});
});
};
render() {
const { data, isLoading } = this.state;
return (
<View style={styles.container}>
{isLoading ? (
<ActivityIndicator />
) : data.data ? (
<FlatList
data={data}
renderItem={({ item }) => <Text>{item}</Text>}
keyExtractor={(item, i) => i.toString()}
/>
) : (
<Text>No Data Available</Text>
)}
</View>
);
}
}
Because AsyncStorage itself is asynchronous read and write, waiting is almost necessary, of course, another way to achieve, for example, to create a memory object, bind the memory object and AsyncStorage, so that you can read AsyncStorage synchronously.
For example, using the following development library can assist you to easily achieve synchronous reading of AsyncStorage react-native-easy-app
import { XStorage } from 'react-native-easy-app';
import { AsyncStorage } from 'react-native';
// or import AsyncStorage from '#react-native-community/async-storage';
export const RNStorage = {
token: undefined,
isShow: undefined,
userInfo: undefined
};
const initCallback = () => {
// From now on, you can write or read the variables in RNStorage synchronously
// equal to [console.log(await AsyncStorage.getItem('isShow'))]
console.log(RNStorage.isShow);
// equal to [ await AsyncStorage.setItem('token',TOKEN1343DN23IDD3PJ2DBF3==') ]
RNStorage.token = 'TOKEN1343DN23IDD3PJ2DBF3==';
// equal to [ await AsyncStorage.setItem('userInfo',JSON.stringify({ name:'rufeng', age:30})) ]
RNStorage.userInfo = {name: 'rufeng', age: 30};
};
XStorage.initStorage(RNStorage, AsyncStorage, initCallback);

use for loop in flatlist react native

I'm beginner in react native, and I want to use for loop in flatlist to push require data,
render() {
return (
<View style={styles.container}>
<FlatList
data={[
require("./assest/image1.jpg"),
require("./assest/image2.jpg"),
require("./assest/image3.jpg"),
]}
renderItem={({ item }) => {
return <ListItem image={item} />
}}
keyExtractor={
(index) => {return index}
}
/>
</View>
)
}
}
Like when pushing array from state using for loop
state ={
a: [12 , 13 , 14 ,15 , 19 ,21 ]
b: "1"
d = () => {
let c =[];
for (var i =0; i<= this.state.a.length - 1 ; i++) {
c.push( <child text = {this.state.a[i] />);
}
return c;
};
Is there anyway to use looping in flatlist or we can't using any looping in list or flatlist in
React Native.
You can define a state (array) and then loop through your required data and push them into the array state. Then, pass that state into the data prop of the FLatlist.
You can do something like this...
import React, { Component } from 'react';
import { View, FlatList } from 'react-native';
import ListItem from '...'; //import the ListItem here
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount = () => {
let data = [];
const images = {
image1: require("./assest/image1.jpg"),
image2: require("./assest/image2.jpg"),
image3: require("./assest/image3.jpg"),
}
images.forEach(image => {
data.push(image);
this.setState({ data })
})
}
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
renderItem={({ item }) => {return <ListItem image={item} />}}
keyExtractor={(index) => {return index}}
/>
</View>
);
}
}
Here, when the component mounts, it loops through the 'images' object and push them into an array called 'data' and store it to the state. Then, that state is passed to the Flatlist as a prop.
Please go through this and ask me if you have any further questions regarding this.

React-Native How to have a different state for each item

I have a component where when I click on an icon, I execute a function that modify a state and then i can check the state and modify the icon. In that comonent, I am mapping datas and it renders several items.
But when I click on one icon all the icons of the components change too.
Here is the code for the component
export default class DiscoveryComponent extends Component {
constructor(props) {
super(props)
this.state = {
starSelected: false
};
}
static propTypes = {
discoveries: PropTypes.array.isRequired
};
onPressStar() {
this.setState({ starSelected: !this.state.starSelected })
}
render() {
return (
this.props.discoveries.map((discovery, index) => {
return (
<Card key={index} style={{flex: 0}}>
<CardItem>
<TouchableOpacity style={[styles.star]}>
<Icon style={[styles.iconStar]} name={(this.state.starSelected == true)?'star':'star-outline'} onPress={this.onPressStar.bind(this)}/>
</TouchableOpacity>
</CardItem>
</Card>
)
})
);
}
}
And here is the code for my screen that uses the component
export default class DiscoveryItem extends Component {
constructor(props) {
super(props);
this.state = {
discoveries: [],
loading: true
};
}
componentDidMount() {
firebase.database().ref("discoveries/").on('value', (snapshot) => {
let data = snapshot.val();
let discoveries = Object.values(data);
this.setState({discoveries: discoveries, loading: false});
});
}
render() {
return (
<Container>
<Content>
<DiscoveryComponent discoveries={this.state.discoveries} />
</Content>
</Container>
)
}
}
Your initiation is correct but you are missing INDEX of each item. Inside this.onPressStar() method check if item's index = currentItem. Also don't forget to set item id = index onpress.
I hope this has given you idea how to handle it.
You have to turn your stars into an Array and index them:
change your constructor:
constructor(props) {
super(props)
this.state = {
starSelected: []
};
}
change your onPressStar function to :
onPressStar(index) {
this.setState({ starSelected[index]: !this.state.starSelected })
}
and your icon to
<Icon style={[styles.iconStar]} name={(this.state.starSelected[index] == true)?'star':'star-outline'} onPress={()=>this.onPressStar(index)}/>
Well, the problem is that you have a single 'starSelected' value that all of your rendered items in your map function are listening to. So when it becomes true for one, it becomes true for all.
You should probably maintain selected state in the top level component, and pass down the discovery, whether its selected, and how to toggle being selected as props to a render function for each discovery.
export default class DiscoveryItem extends Component {
constructor(props) {
super(props);
this.state = {
discoveries: [],
selectedDiscoveries: [] // NEW
loading: true
};
}
toggleDiscovery = (discoveryId) => {
this.setState(prevState => {
const {selectedDiscoveries} = prevstate
const discoveryIndex = selectedDiscoveries.findIndex(id => id === discoveryId)
if (discoveryIndex === -1) { //not found
selectedDiscoveries.push(discoveryId) // add id to selected list
} else {
selectedDiscoveries.splice(discoveryIndex, 1) // remove from selected list
}
return {selectedDiscoveries}
}
}
componentDidMount() {
firebase.database().ref("discoveries/").on('value', (snapshot) => {
let data = snapshot.val();
let discoveries = Object.values(data);
this.setState({discoveries: discoveries, loading: false});
});
}
render() {
return (
<Container>
<Content>
{
this.state.discoveries.map(d => {
return <DiscoveryComponent key={d.id} discovery={d} selected={selectedDiscoveries.includes(d.id)} toggleSelected={this.toggleDiscovery} />
//<DiscoveryComponent discoveries={this.state.discoveries} />
</Content>
</Container>
)
}
}
You can then use your DiscoveryComponent to render for each one, and you're now maintaining state at the top level, and passing down the discovery, if it is selected, and the toggle function as props.
Also, I think you may be able to get snapshot.docs() from firebase (I'm not sure as I use firestore) which then makes sure that the document Id is included in the value. If snapshot.val() doesn't include the id, then you should figure out how to include that to make sure that you use the id as both key in the map function as well as for the selectedDiscoveries array.
Hope that helps
It works now, thanks.
I've made a mix between Malik and Rodrigo's answer.
Here is the code of my component now
export default class DiscoveryComponent extends Component {
constructor(props) {
super(props)
this.state = {
tabStarSelected: []
};
}
static propTypes = {
discoveries: PropTypes.array.isRequired
};
onPressStar(index) {
let tab = this.state.tabStarSelected;
if (tabStar.includes(index)) {
tabStar.splice( tabStar.indexOf(index), 1 );
}
else {
tabStar.push(index);
}
this.setState({ tabStarSelected: tab })
}
render() {
return (
this.props.discoveries.map((discovery, index) => {
return (
<Card key={index} style={{flex: 0}}>
<CardItem>
<Left>
<Body>
<Text note>{discovery.category}</Text>
<Text style={[styles.title]}>{discovery.title}</Text>
</Body>
</Left>
<TouchableOpacity style={[styles.star]}>
<Icon style={[styles.iconStar]} name={(this.state.tabStarSelected[index] == index)?'star':'star-outline'} onPress={()=>this.onPressStar(index)}/>
</TouchableOpacity>
</CardItem>
</Card>
)
})
);
}
}

React state is array of objects which are react elements

I'm running into an issue right now trying to render a list using react, where I'm saving my react elements into the state, but the problem I'm getting is that the console outputs this:
Uncaught Error: Objects are not valid as a React child (found: object with keys {}). If you meant to render a collection of children, use an array instead.
Here is what the state looks like which causes the error:
export default class UserData extends Component {
constructor() {
super();
this.state = {
resultsItems: {}
}
};
componentDidMount() {
fetch(url)
.then(results => {
return results.json();
}).then(data => {
console.log(data.items);
let items = data.items.map((item) => {
console.log(item.title);
return (
<li>
<h2>item.title</h2>
</li>
)
});
this.setState({resultsItems: items});
console.log("state", this.state.resultsItems);
})
.catch(error => console.log(error))
};
render() {
return (
<div>
<button onClick={() => this.props.updateLoginStatus(false)}>
Logout
</button>
<div>
ID: {this.props.user}
{this.state.resultsItems}
</div>
</div>
)
}
}
By way of demonstrating the sort of thing Hamms is talking about in their comment:
class UserData extends Component {
constructor () {
super()
this.state = {
resultsItems: []
}
}
componentDidMount () {
// Simulate API response
const resultsItems = [
{ title: 'foo' },
{ title: 'bar' },
{ title: 'wombat' }
]
this.setState({ resultsItems })
}
render () {
return (
<div>
{this.state.resultsItems.map(item => <ResultsItem item={item} />)}
</div>
)
}
}
function ResultsItem ({ item }) {
return <li>{item.title}</li>
}
However, Chris' answer is correct as to the cause of the error message: the first render tries to use an empty object and not an array, which fails.
It seems like you are correctly setting an array to your state on componentDidMount, however the initial state in your constructor is an object and not an array!
So change this:
this.state = {
resultsItems: {}
}
to this:
this.state = {
resultsItems: []
}

Categories