Trying to get FlatList to display data from Firebase.
Setup is correct and I can see the date in my console, but don't know how to visualise it.
I'd like to see 'recipeOne' 'recipeTwo' 'recipeThree' in the list.
I am sure I am missing something basic.
Here is the code
...
import {DataConfig} from '../data/DataConfig';
const firebaseApp = firebase.initializeApp(DataConfig);
globalTexts = require('../styles/Texts.js');
globalColors = require('../styles/Colors.js');
export default class ListSort extends Component {
constructor(props) {
super(props);
this.dataItem = firebaseApp.database().ref('recipes');
this.state = {
item: []
}
};
componentWillMount() {
this._createItemList();
};
_createItemList = (dataItem) => {
this.dataItem.on('value', (snapshot) => {
var itemList = [];
snapshot.forEach((doc) => {
itemList.push({
key:doc.key,
itemType: doc.toJSON()
});
this.setState({item: itemList});
console.log(this.state.item);
})
})
};
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.item}
renderItem={({item, index}) => (
<View style={styles.cell}>
<Text style={globalText.btnFlatPrimary}>{item.recipes}</Text>
</View>
)}
/>
</View>
)
}
}
and here is the data. The rules in Firebase are setup as read:true only.
{
"recipes": {
"recipeOne": {...
"recipeTwo": {...
"recipeThree": {...
}
}
Related
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);
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>
)
})
);
}
}
I'm trying to display/store a list of items in my flatlist, but the problem is when I save an item and load that item in a different screen it is in a kind of repetition(look for the screen shot). And when I try to add different item, this new item will replace the previous item with the same kind of repetition. What I'm targeting is to have a list.
List_ScreenShot
Here is my code
AddModal.js
export default class AddModal extends React.Component {
constructor(props) {
super(props);
this.state = {
modalVisible: props.modalVisible,
id: null,
count: 0,
price: null
};
}
state = {
text: '',
}
save = () => {
const { text } = this.state;
let myArray = {
text, text
}
AsyncStorage.setItem('myArray', JSON.stringify(myArray));
alert(text + 'saved');
}
onChange = (text) => {
this.setState({ text });
}
componentWillReceiveProps(nextProps) {
this.setState({
modalVisible: nextProps.modalVisible,
id: nextProps.id,
price: nextProps.price
})
}
render() {
console.log('inside AppModal', this.state.modalVisible);
return (
<View>
<TextInput style = { styles.input }
keyboardType = "numeric"
onChangeText = { this.onChange }
value = { this.state.text } //Item **
>
</TextInput>
</View>
<View}>
<TouchableOpacity
onPress = {() => { this.props.setModalVisible(false) }}
>
<Text style = { styles.buttonText }>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity
onPress = { this.save }>
<Text style = { styles.buttonText }>Send</Text>
</TouchableOpacity>
</View>
)
}
}
Settlment.js
import Details from '../Menus/Details';
const key = '#MyApp:key';
export default class Settlement extends React.Component {
state = {
text: '',
storedValue: '',
myArray: ''
}
componentWillMount() {
//this.onLoad();
AsyncStorage.getItem('myArray')
.then(text => this.setState({ text }));
}
showData = async() => {
let myArray = await AsyncStorage.getItem('myArray');
let d = JSON.parse(myArray);
this.setState({ myArray : myArray });
}
render() {
const { myArray, text } = this.state;
return (
<View>
<TouchableOpacity onPress = {this.showData}>
<Text>Load Data</Text>
</TouchableOpacity>
<FlatList data = { this.state.myArray }
renderItem = {({ item }) =>
<Text>{myArray}</Text>
}
keyExtractor={(item, index) => index.toString()}
>
</FlatList>
</View>
);
}
}
What I see here:
const { text } = this.state;
let myArray = {
text, text
}
AsyncStorage.setItem('myArray', JSON.stringify(myArray));
alert(text + 'saved');
is an object called myArray, and nothing is being added to it. It's being defined and then assigned a value.
Maybe you could declare your array elsewhere like in the constructor (as an array, not an object, using myArray = []) and then use myArray.push(text) or if you want an array containing objects you can push object using myArray.push({ yourKeyName: text }). Also, it seems like the object you're storing in AsyncStorage is being replaced and not added to. But I'm not sure why you're getting multiple list items instead of just one.
PS - Where you're declaring state looks a bit off. I usually see it like this:
constructor() {
super();
this.state = {
text: '',
storedValue: '',
myArray: '',
};
}
I'm trying to create dynamic checkbox with the name fetching from json, this issue looks same as I need, but without the code explaining, I can't archieve my goal,
I have a json example like this :
this.state = {
data : [
{
"name": "ALL",
},
{
"name": "Android",
},
{
"name": "iOS",
},
{
"name": "React Native",
}
]}
and with this code below:
<CheckBox
center
title={this.state.data[1].name}
onPress={() => {this.setState({checked: !this.state.checked})}}
checked={this.state.checked}
/>
the checkbox running well but it's just showing 2nd value of json
My Goal is to displaying all of json value into flatlist and makes checkbox running well,
For now I just can displaying those json into FlatList, but the checkbox is not works
import React, { Component } from 'react';
import {
Text, View, StyleSheet, Alert, FlatList
} from 'react-native';
import Dimensions from 'Dimensions';
import { CheckBox } from 'react-native-elements'
const DeviceWidth = Dimensions.get('window').width;
const DeviceHeight = Dimensions.get('window').height;
class MedicalClearlance extends React.Component {
constructor(props){
super(props);
this.state = {
checked:[],
data : [
{
"name": "ALL",
},
{
"name": "Android",
},
{
"name": "iOS",
},
{
"name": "React Native",
}
]}
}
render() {
return (
<FlatList
data={ this.state.data }
renderItem={({item, index}) =>
<CheckBox
center
title={item.name}
onPress={() => {this.setState({checked: !this.state.checked}), console.log(this.state.checked +' '+ index)}}
checked={this.state.checked}/>
}
/>
);
}
}
anyone can help me how to archieve my goal?
The answer that Ahsan Ali provided will work. However it is missing a very vital line of code.
Within the <FlatList/> component, be sure to add this extraData
={this.state}. This will allow the FlatList component to re-render whenever the state is changed.
The render method will then look like this:
handleChange = (index) => {
let checked = [...this.state.checked];
checked[index] = !checked[index];
this.setState({ checked });
}
render() {
let { data, checked } = this.state;
return (
<FlatList
data={data}
extraData={this.state}
renderItem={({ item, index }) =>
<CheckBox
center
title={item.name}
onPress={() => this.handleChange(index)}
checked={checked[index]} />
}
/>
);
}
By passing extraData={this.state} to FlatList we make sure FlatList
itself will re-render when the state.selected changes. Without setting
this prop, FlatList would not know it needs to re-render any items
because it is also a PureComponent and the prop comparison will not
show any changes.
More information can be found at React-Native Flat-List documentation here.
If you're using the code from Ahsun Ali's post, there may be another error you come across.
A warning error displays that the componentWillMount() method is
deprecated. In which case be sure to use the componentDidMount()
instead.
Hope this helps people!
You need to fill up the checked array in order to manipulate it after.
constructor() {
super();
this.state = {
data: [
{
"name": "ALL",
},
{
"name": "Android",
},
{
"name": "iOS",
},
{
"name": "React Native",
}
],
checked: []
}
}
componentWillMount() {
let { data, checked } = this.state;
let intialCheck = data.map(x => false);
this.setState({ checked: intialCheck })
}
and pass the index of the selected checkbox to update it
handleChange = (index) => {
let checked = [...this.state.checked];
checked[index] = !checked[index];
this.setState({ checked });
}
render() {
let { data, checked } = this.state;
return (
<FlatList
data={data}
renderItem={({ item, index }) =>
<CheckBox
center
title={item.name}
onPress={() => this.handleChange(index)}
checked={checked[index]} />
}
/>
);
}
I hope it helps!
you could try this for multiple selection, ref link-> https://facebook.github.io/react-native/docs/flatlist
class MyListItem extends React.PureComponent
{
_onPress = () => {
this.props.onPressItem(this.props.id);
};
render() {
const textColor = this.props.selected ? 'red' : 'black';
return (
<TouchableOpacity onPress={this._onPress}>
<View>
<Text style={{color: textColor}}>{this.props.title}</Text>
</View>
</TouchableOpacity>
);
}
}
class MultiSelectList extends React.PureComponent {
state = {selected: (new Map(): Map<string, boolean>)};
_keyExtractor = (item, index) => item.id;
_onPressItem = (id: string) => {
// updater functions are preferred for transactional updates
this.setState((state) => {
// copy the map rather than modifying state.
const selected = new Map(state.selected);
selected.set(id, !selected.get(id)); // toggle
return {selected};
});
};
_renderItem = ({item}) => (
<MyListItem
id={item.id}
onPressItem={this._onPressItem}
selected={!!this.state.selected.get(item.id)}
title={item.title}
/>
);
render() {
return (
<FlatList
data={this.props.data}
extraData={this.state}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
/>
);
}
}
for(const ele of this.state.selected.keys())
console.log(ele);
//**after that you could get all the selected keys or values from something like this**
I have a project in react-native (0.23) with Meteor 1.3 as back-end and want to display a list of contact items. When the user clicks a contact item, I would like to display a checkmark in front of the item.
For the connection to Meteor DDP I use the awesome library inProgress-team/react-native-meteor.
import Meteor, { connectMeteor, MeteorListView, MeteorComplexListView } from 'react-native-meteor';
class ContactsPicker extends React.Component {
constructor(props) {
super(props);
this.state = {
subscriptionIsReady: false
};
}
componentWillMount() {
const handle = db.subscribe("contacts");
this.setState({
subscriptionIsReady: handle.ready()
});
}
render() {
const {subscriptionIsReady} = this.state;
return (
<View style={gs.standardView}>
{!subscriptionIsReady && <Text>Not ready</Text>}
<MeteorComplexListView
elements={()=>{return Meteor.collection('contacts').find()}}
renderRow={this.renderItem.bind(this)}
/>
</View>
);
}
The first problem is, that subscriptionIsReady does not trigger a re-render once it returns true. How can I wait for the subscription to be ready and update the template then?
My second problem is that a click on a list item updates the state and should display a checkmark, but the MeteorListView only re-renders if the dataSource has changed. Is there any way to force a re-render without changing/ updating the dataSource?
EDIT 1 (SOLUTION 1):
Thank you #user1078150 for providing a working solution. Here the complete solution:
'use strict';
import Meteor, { connectMeteor, MeteorListView, MeteorComplexListView } from 'react-native-meteor';
class ContactsPicker extends React.Component {
getMeteorData() {
const handle = Meteor.subscribe("contacts");
return {
subscriptionIsReady: handle.ready()
};
}
constructor(props) {
super(props);
this.state = {
subscriptionIsReady: false
};
}
componentWillMount() {
// NO SUBSCRIPTION HERE
}
renderItem(contact) {
return (
<View key={contact._id}>
<TouchableHighlight onPress={() => this.toggleSelection(contact._id)}>
<View>
{this.state.selectedContacts.indexOf(contact._id) > -1 && <Icon />}
<Text>{contact.displayName}</Text>
</View>
</TouchableHighlight>
</View>
)
}
render() {
const {subscriptionIsReady} = this.data;
return (
<View>
{!subscriptionIsReady && <Text>Not ready</Text>}
<MeteorComplexListView
elements={()=>{return Meteor.collection('contacts').find()}}
renderRow={this.renderItem.bind(this)}
/>
</View>
);
}
}
connectMeteor(ContactsPicker);
export default ContactsPicker;
You have to do something like this :
getMeteorData() {
const handle = Meteor.subscribe("contacts");
return {
ready: handle.ready()
};
}
render() {
const { ready } = this.data;
}