Printing data with specific key from Firebase - javascript

In my React Native app I am trying to print data from my Firebase database which have specific key (passed from another component). I have used similiar code I had used to get all database data but I am getting blank space.
var data = []
var currentUser;
class FavPlant extends React.Component {
constructor(props){
super(props)
this.ds = new ListView.DataSource({rowHasChanged:(r1,r2) => r1 !==r2})
this.state = {
listViewData : data
}
}
componentDidMount(){
const { navigation } = this.props;
const keyPlant = navigation.getParam('keyPlant');
firebase.auth().onAuthStateChanged((user) => {
if (user != null) {
var that = this
firebase.database().ref(user.uid).child('plantList').equalTo('keyPlant').on('child_added',function(data){
var newData = [...that.state.listViewData]
newData.push(data)
that.setState({ listViewData: newData})
});
}
})
}
render(){ return(
<Container>
<Content>
<ListView
enableEmptySections
dataSource = {this.ds.cloneWithRows(this.state.listViewData)}
renderRow={data =>
<Text>{data.val().bloom}</Text>
}/>
</Content>
</Container>
)}

If you have a DatabaseReference to the plantList element in your JSON:
var plantListRef = ...
Then you can get the child node with a specific key with:
plantListRef.child("-Lg-l2GOOVC_Hni5a3ng").on('value',function(snapshot){
console.log(snapshot.key); // prints "-Lg-l2GOOVC_Hni5a3ng"
console.log(snapshot.val());
});

Related

how to merge local setState list to Redux list into one list - redux react

Here i have a difficult situation. I have a locationData json in JobsPanel component which is saving location details based on one id(jobId). Now, in my component i have a part 'Configured Location' where i am calling the saved location data and make a setState list ('configuredList') from that json. Now, i have one more part in my application preliminary locations data using redux action calling other api and save into a list 'conLocations'.
Now, i am adding one location item 'conLocation' list (redux state) to 'configuredList'(setState) and updating the changes. It is working fine but last added item showing two times. After trial, i do understand that i have rendered two mapped list. How to merge that into one ? I have done so far this.
configLocation function where i am retrieving last saved location from locationData json.
/** Currently initialize and configure configuredList for retrieving existing job's location data */
configLocation(locationData) {
let configuredList = [];
if (locationData.locations.locationDetails != null && locationData.locations.locationDetails != undefined) {
locationData.locations.locationDetails.map(item => {
let listitem = { ...item };
configuredList.push(listitem);
});
}
this.setState({ configuredList });
}
getLocationData function where i am merging two list that retrieved list and conLocations list and i am calling this function to other component where save changes or update changes operation is happening. It is working fine.
getLocationData() {
let saveableLocationlist = [];
if (this.props.conLocations != null && this.state.configuredList != null) {
const { configuredList } = this.state;
const { conLocations } = this.props;
let totalList = configuredList.concat(conLocations);
saveableLocationlist = totalList;
}
const locationData = {
locationDetails: saveableLocationlist
}
return locationData;
}
here you can see i am updating the locationData json . By calling this function in jobspanel that updated locationData json is now available for my component in 'configLocation' function.
My component code:
export class NewLocationPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
configuredList: [],
chkitems: []
};
this.configLocation = this.configLocation.bind(this);
this.togglePanel = this.togglePanel.bind(this);
this.handleClick = this.handleClick.bind(this);
this.allLocations = this.allLocations.bind(this);
this.clearall = this.clearall.bind(this);
this.getLocationData = this.getLocationData.bind(this);
this.handleRemove = this.handleRemove.bind(this);
this.removeConfigLocation = this.removeConfigLocation.bind(this);
this.removeLocationAll = this.removeLocationAll.bind(this);
this.handleChecklocation = this.handleChecklocation.bind(this);
this.handleCheckedAdded = this.handleCheckedAdded.bind(this);
this.handleCheckedRemove = this.handleCheckedRemove.bind(this);
this.handleActionButton = this.handleActionButton.bind(this);
}
componentDidMount() {
this.props.loadData();
if (this.props.locationData != null && this.props.locationData != undefined) {
this.configLocation(this.props.locationData);
}
}
componentDidUpdate(prevProps, prevState) {
if ((prevProps.jobId != this.props.jobId || prevProps.locationData != this.props.locationData)) {
this.configLocation(this.props.locationData);
}
}
//other codes
/** Currently initialize and configure configuredList for retrieving existing job's location data */
configLocation(locationData) {
let configuredList = [];
if (locationData.locations.locationDetails != null && locationData.locations.locationDetails != undefined) {
locationData.locations.locationDetails.map(item => {
let listitem = { ...item };
configuredList.push(listitem);
});
}
this.setState({ configuredList });
}
/** updating locationData by saving changes - calling this function into jobsPanel */
getLocationData() {
let saveableLocationlist = [];
if (this.props.conLocations != null && this.state.configuredList != null) {
const { configuredList } = this.state;
const { conLocations } = this.props;
let totalList = configuredList.concat(conLocations);
saveableLocationlist = totalList;
}
const locationData = {
locationDetails: saveableLocationlist
}
return locationData;
}
//other codes
render() {
//const{configuredList} = this.state;
const _labels = store.getLabels();
let collapsedToggle = this.props.open ? 'collapsed' : ''
return (
{this.state.open ? (
<div className="panel-body">
<div className="row grid-divider">
<div className="col-sm-6">
<div className="col-padding">
<div className="pos-div"><h4>Configured Location</h4>
<div><table className="table configTableColor"><thead>{this.state.configuredList.map((locc, index) => <tr key={index}><th><input type="checkbox" onClick={() => this.handleCheckedRemove(locc.mruCode)} /><label></label></th><th className="configLocationInfo">{locc.mruCode} - {_labels[locc.division]} - {locc.country}</th><th className="text-right"><img alt="DeleteIcon" onClick={() => { this.removeConfigLocation(index) }} className="deleteIconStyle" src="img/delete_large_active.png" /></th></tr>)}</thead>
<tbody>
{this.props.conLocations.map((loct, index) => <tr key={index}>
<td><input type="checkbox" /><label></label></td>
<td className="configLocationInfo">{loct.mruCode} - {_labels[loct.division]} - {loct.country}</td>
<td className="text-right"><img alt="DeleteIcon" onClick={() => this.handleRemove(loct.mruCode)} className="deleteIconStyle" src="img/delete_large_active.png" /></td>
</tr>
)}
</tbody></table></div>
</div>
</div>
</div>
</div>) : null}
</div>
);
}
}
const mapStateToProps = state => {
return {
location: state.locationRed.location,
conLocations: state.locationRed.conLocations,
isChecked: state.locationRed.isChecked
};
};
const mapDispatchToProps = (dispatch) => {
return {
loadData: () => { dispatch(loadData()) },
addLocation: (mruCode) => { dispatch(addLocation(mruCode)) },
addAllLocation: () => { dispatch(addAllLocation()) },
removeLocation: (mruCode) => { dispatch(removeLocation(mruCode)) },
removeAllLocation: () => { dispatch(removeAllLocation()) },
checkboxState: (mruCode) => { dispatch(checkboxState(mruCode)) },
checkedLocation: () => { dispatch(checkedLocation()) }
}
}
export default connect(mapStateToProps, mapDispatchToProps, null, { withRef: true })(NewLocationPanel);
As you can see i am rendering two list. How to merged into one?
Jobs Panel component where i am initialize and saving locationData details
import React from 'react';
import ReactDOM from 'react-dom';
import LocationPanel from '../panels/NewLocationPanel';
class JobsPanelComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
jobDetailJson: this.props.jobDetailJson
};
this.setLocationPanelRef = cRef =>{this.locationPanel = cRef;};
}
componentWillUnmount() {
this.clearStates();
this.clearRefs();
this.clearBindings();
}
clearStates() {
this.state.jobDetailJson = null;
}
clearRefs(){
this.locationPanel = null;
}
clearBindings(){
this.setLocationPanelRef = null;
}
componentWillMount() {
this.state.jobDetailJson = this.props.jobDetailJson;
}
componentWillReceiveProps(nextProps) {
this.state.jobDetailJson = nextProps.jobDetailJson;
}
saveJobData(jobData){
var locationData = null;
if(some conditions){
locationData = this.locationPanel.getWrappedInstance().getLocationData();
}
//more other lines not related to my mine
}
render(){
var locationDataJson= null;
if(this.state.jobDetailJson != null){
locationDataJson =this.state.jobDetailJson;
}
return(<div className="panel-group" id="jobsPanelGroup">
<LocationPanel ref={this.setLocationPanelRef} locationData ={locationDataJson} jobDetailJson={this.state.jobDetailJson} versionId={versionId} jobName={jobName} jobId={jobId} isForViewOnly={this.props.isForViewOnly} parentJobId={this.props.parentJobId} title="Location"/>
//More coded lines for other things not related to my part
);
}
}
My application flow will be like - Configured Location(initial) configuredList -> conLocations (redux list) -> conLocations(add item) -> Configured Location(intermediate) configuredList + added item(conLocations) -> save changes -> Configured Location(final) - merged List
save changes /update locationData everything is in Jobs Panel but working fine. There is no problem. How to make changes in my component.
The mapStateToProps function is passed both the redux state and the component's props. So you can combine your locations from redux and from props inside mapStateToProps:
// destructuring only the things we need from state (locationRed) and props (locationData)
const mapStateToProps = ({ locationRed }, { locationData }) => ({
location: locationRed.location,
// get a merged set
conLocations: [...locationRed.conLocations, ...(locationData.locations.locationDetails || [])],
isChecked: locationRed.isChecked
})
With this setup you could most likely eliminate your configuredList state and related update functions, your componentDidUpdate function and just render from props.conLocations instead of from state and props in two separate loops.
You could also dedupe locations or do any job id checks you need inside of mapStateProps when merging your lists. If it starts to gets a bit complicated in your mapStateToProps, you could take a look at memoized selectors like reselect that would make that a lot nicer.

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-Native Firebase Display array of objects using FlatList

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>
);
}
}

How to re-render React Native Component after transferring prop to there

I'm trying to implement a list of users with searching by username possibility.
I've faced with the issue with re-rendering SearchListOfUsers after I change a prop usernameFilter in a parent Component SearchPeopleScreen and pass it to a child SearchListOfUsers.
I know that a component should re-render itself when its state is changed but in my case even state of child component doesn't change. How to update my child comp. SearchListOfUsers after I pass a prop usernameFilter?
Here is my parent comp. SearchPeopleScreen:
export default class SearchPeopleScreen extends Component {
constructor(props) {
super(props);
this.state = {
...
usernameFilter: ''
}
}
render() {
return(
<Container>
<Header style = {searchPeopleScreenStyle.header} searchBar>
<Title style = {searchPeopleScreenStyle.title}>
Search
</Title>
<Right/>
<Item style = {searchPeopleScreenStyle.searchFieldWrapper}>
<IconSimpleLine name = 'magnifier' color = {placeholder} size = {20} style = {{padding: 10}}/>
<TextInput
underlineColorAndroid = 'transparent'
onChangeText = {(text) => {
this.setState({usernameFilter: text});
}}
placeholder = 'Type username'
style = {searchPeopleScreenStyle.searchInput}
maxLength = {15}
/>
</Item>
</Header>
<Content>
<ScrollView contentContainerStyle = {searchPeopleScreenStyle.container}>
...
{/* Search screen's body */}
<SearchListOfUsers searchOption = {this.state.searchOption}
usernameFilter = {this.state.usernameFilter}/>
</ScrollView>
</Content>
</Container>
)
}
}
And here is my child comp. SearchListOfUsers:
export default class SearchListOfUsers extends Component {
constructor(props) {
super(props);
this.state = {
usersDataArray: [],
usernameFilter: this.props.usernameFilter
};
this.arrayHolder = [];
console.warn('1 - ' + this.state.usernameFilter)
}
componentDidMount() {
this.getAllUsersData()
console.warn(this.state.usernameFilter)
if(this.state.usernameFilter) {
this.filterUsers();
}
}
getAllUsersData = () => {
return new Promise((resolve, reject) => {
// getting users data and creating an array
...
allUsersDataArray.push({...});
this.setState({
usersDataArray: allUsersDataArray
});
resolve();
})
}
filterUsers = () => {
const newUsersDataArray = this.arrayHolder.filter((user) => {
const usernameInTheList = user.userUsername.toUpperCase();
const inputtedUsername = this.state.usernameFilter.toUpperCase();
return usernameInTheList.includes(inputtedUsername);
});
this.setState({
usersDataArray: newUsersDataArray
})
}
render() {
return(
<Content contentContainerStyle = {searchPeopleScreenStyle.listOfUsersWrapperGlobal}>
<FlatList
data = {this.state.usersDataArray}
keyExtractor = {(item) => (item.userId)}
renderItem = {({item}) => (
<UserListItem
country = {item.userCountry}
username = {item.userUsername}
...
/>
)}
/>
</Content>
)
}
}
}
If you need to filter your data based on selections from a parent component, you should also be filtering your collection there as well. Once you have filtered your collection, that should be passed to the child component.
The child component in this case should be purely presentational and static. It shouldn't care about filtering data or updating it's component state etc, it just wants to render out whatever props it is passed. searchOption, usernameFilter, dataCollection
You filter users in componentDidMount lifecyle method which means it will run only once on mounting process of child component.
You can filter in render method like
filterUsers = () => {
if(!this.props.usernameFilter.length) return this.state.usersDataArray
return this.state.usersDataArray.map((user) => {
const usernameInTheList = user.userUsername.toUpperCase();
const inputtedUsername = this.props.usernameFilter.toUpperCase();
return usernameInTheList.includes(inputtedUsername);
});
}
render() {
return(
<Content contentContainerStyle = {searchPeopleScreenStyle.listOfUsersWrapperGlobal}>
<FlatList
data = {this.filterUsers()}
keyExtractor = {(item) => (item.userId)}
renderItem = {({item}) => (
<UserListItem
country = {item.userCountry}
username = {item.userUsername}
...
/>
)}
/>
</Content>
)
}
}

clonewithrows array issue in react-native

I am trying to populate a Listview in my react-native app using data from Firebase and am currently getting this error:
"Objects are not valid as a React child (found object with keys {title}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React addons. Check the render method of 'Text.'"
This is occurring on my call to clonewithrows. Currently my input to this function (after parsing response to Firebase) is: [ { title: 'New York' }, { title: 'Boston' } ]
Here is my relevant code; please let me know if you see anything that may be causing the problem.
const huntsRef = new Firebase(`${ config.FIREBASE_ROOT }/hunts`)
class Home extends Component {
constructor(props) {
super(props);
var conDataSource = new ListView.DataSource(
{rowHasChanged: (r1, r2) => r1.guid != r2.guid});
this.state = {
dataSource: conDataSource
};
}
listenForItems(huntsRef) {
huntsRef.on('value', (snap) => {
var hunts = [];
snap.forEach((child) => {
hunts.push({
title: child.val().title
});
});
this.setState({
dataSource: this.state.dataSource.cloneWithRows(hunts)
});
console.log('datasource' + this.state.dataSource);
});
}
componentDidMount() {
this.listenForItems(huntsRef);
}
renderRow(rowData, sectionID, rowID) {
console.log("ROWDATA" + rowData);
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <Text>{rowData}</Text>}
/>
);
}
}
module.exports = Home;
As the message says, rowData is javascript object and cannot be rendered. You can stringify your rowData. Ex:
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <Text>{JSON.stringify(rowData)}</Text>}
/>

Categories