I am working on a simple to do application linked to firebase using react native, I am facing a problem when I wanna render the information.
my render method looks something like this:
renderRow(rowData) {
return (
<TouchableHighlight
underlayColor='#dddddd'
onPress={() => this.removeTodo(rowData)}>
<View>
<View style={styles.row}>
<Text style={styles.todoText}>{rowData.text}</Text>
</View>
<View style={styles.separator} />
</View>
</TouchableHighlight>
);
}
problem is that this is an object rowData.text and not a string as it should be
this is part of my method for pushing the info to firebase:
this.itemsRef.on('child_added', (dataSnapshot) => {
this.items.push({id: dataSnapshot.key(), text: dataSnapshot.val()});
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
firebase documentation only states that .val() may return a primitive (string, number, or boolean), an array, or an object. It may also return null. I know that in my case it's returning an object, but I don't know why, nor how to change it to string?
Edit:
Here's the class i am working on:
class ToDo_React extends Component {
// Your App Code
constructor(props) {
super(props);
var myFirebaseRef = new Firebase('https://resplendent-heat-7859.firebaseio.com');
this.itemsRef = myFirebaseRef.child('items');
this.state = {
newTodo: '',
todoSource: new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2})
};
this.items = [];
}
componentDidMount() {
// When a todo is added
this.itemsRef.on('child_added', (dataSnapshot) => {
this.items.push({id: dataSnapshot.key(), text: dataSnapshot.val()});
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
// When a todo is removed
this.itemsRef.on('child_removed', (dataSnapshot) => {
this.items = this.items.filter((x) => x.id !== dataSnapshot.key());
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
}
addTodo() {
if (this.state.newTodo !== '') {
this.itemsRef.push({
todo: this.state.newTodo
});
this.setState({
newTodo : ''
});
}
}
removeTodo(rowData) {
this.itemsRef.child(rowData.id).remove();
}
removeTodo(rowData) {
this.itemsRef.child(rowData.id).remove();
}
render() {
return (
<View style={styles.appContainer}>
<View style={styles.titleView}>
<Text style={styles.titleText}>
My Todos
</Text>
</View>
<View style={styles.inputcontainer}>
<TextInput style={styles.input} onChangeText={(text) => this.setState({newTodo: text})} value={this.state.newTodo}/>
<TouchableHighlight
style={styles.button}
onPress={() => this.addTodo()}
underlayColor='#dddddd'>
<Text style={styles.btnText}>Add!</Text>
</TouchableHighlight>
</View>
<ListView
dataSource={this.state.todoSource}
renderRow={this.renderRow.bind(this)} />
</View>
);
}
renderRow(rowData) {
return (
<TouchableHighlight
underlayColor='#dddddd'
onPress={() => this.removeTodo(rowData)}>
<View>
<View style={styles.row}>
<Text style={styles.todoText}>{rowData.text}</Text>
</View>
<View style={styles.separator} />
</View>
</TouchableHighlight>
);
}
}
I believe you're using ListView's DataSource wrong. I may be wrong, but I don't think you're suppose to override your initial ListView.DataSource instance. Give this a go.
constructor(props) {
super(props);
var myFirebaseRef = new Firebase('https://resplendent-heat-7859.firebaseio.com');
this.itemsRef = myFirebaseRef.child('items');
this.items = [];
this.ds = new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2})
this.state = {
newTodo: '',
todoSource: this.ds.cloneWithRows([])
};
}
componentDidMount() {
this.itemsRef.on('child_added', (dataSnapshot) => {
this.items.push({id: dataSnapshot.key(), text: dataSnapshot.val()});
this.setState({
todoSource: this.ds.cloneWithRows(this.items)
});
});
this.itemsRef.on('child_removed', (dataSnapshot) => {
this.items = this.items.filter((x) => x.id !== dataSnapshot.key());
this.setState({
todoSource: this.ds.cloneWithRows(this.items)
});
});
}
Just in case you didn't figure this one out, you have to add val().todo to make this work. I added more code so that ListView will render on initial load. Hope this helps!
componentDidMount() {
this.setState({
todoSource: this.state.todoSource
});
// When a todo is added
this.itemsRef.on('child_added', (dataSnapshot) => {
this.items.push({id: dataSnapshot.key(), text: dataSnapshot.val().todo});
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
Related
I am new to coding. Currently tryin to make a mobile app in react-native + firebase.
I am stuck at some really simple ( as i think ) stage, but spent a couple of days now but cannot find an answer.
In my app, user can create or enter room. Once created, room generates specific folder in firebase.
Creating and entering rooms work fine.
But whhen user enters a new room , he observes the state from the previous room even though it suppose to be blank as this is his first entry.
I dont understand how to solve this problem, absolutely crying now. A
Any help is appreciated.
This is a part of code where state is saved:
class ChatRoom extends React.Component {
constructor(zaza) {
super(zaza);
this.state = {
allTasksComplete: false,
task1: false,
task2: false,
task3: false,
task4: false,
task5: false,
task6: false,
task7: false,
finishedTasks: null,
zozo: false,
newRoomName: '',
creator: '',
};
}
generateTask = async (taskIndex, taskName,) => {
const creatorCheck = await AsyncStorage.getItem('nickname')
this.setState({creator: creatorCheck})
const nicknameSnap = await collectionRef.where('roomName', '==', this.state.newRoomName).get()
const nickCheck = nicknameSnap.docs[0].data()
if(nickCheck.creator === this.state.creator) {
firestore()
.collection('Rooms')
.doc(this.state.newRoomName)
.collection('Alpha')
.doc(taskIndex)
.set({
taskText: taskName,
})
.then(() => {
console.log('Task added')
})
}
else {
firestore()
.collection('Rooms')
.doc(this.state.newRoomName)
.collection('Beta')
.doc(taskIndex)
.set({
taskText: taskName,
})
.then(() => {
console.log('Task added')
})
} }
async componentDidMount() {
try {
const trytextx = await AsyncStorage.getItem('currentRoom')
const trial = JSON.parse(trytextx)
const maybe1 = await AsyncStorage.getItem('task1')
const string1 = JSON.parse(maybe1)
const maybe2 = await AsyncStorage.getItem('task2')
const string2 = JSON.parse(maybe2)
this.setState({newRoomName: trial})
if (string1 === false) {
this.setState({task1: false})
} else {
this.setState({ task1: string1 })
}
if (string2 === false) {
this.setState({task2: false})
} else {
this.setState({ task2: string2 })
}
} catch (e) {
console.log(e)
}
db.collection('Rooms')
.doc('RRN')
.collection('Alpha')
.get()
.then(snapshot => {
const tasks = []
snapshot.forEach(doc => {
const data = doc.data()
tasks.push(data)
})
this.setState({
finishedTasks: tasks
})
})
}
taskOne() {
if (this.state.task1) {
return (
<View>
<TaskOne />
<TouchableOpacity onPress={() => {
this.generateTask('Task1', 'First Task');
this.buttonTaskTwo()
}} raised='true' >
<View style={styles.buttonDone2}>
<Text style={styles.buttonText2}>
Выполнено
</Text>
</View>
</TouchableOpacity>
</View>
)
}
}
taskTwo() {
if (this.state.task2) {
return (
<View>
<TaskTwo />
<TouchableOpacity onPress={() => {
this.generateTask('Task2', 'Second Task');
this.buttonTaskThree()
}}>
<View style={styles.buttonDone2}>
<Text style={styles.buttonText2}>
Выполнено
</Text>
</View>
</TouchableOpacity>
</View>
)
}
}
CongratsMsg() {
if (this.state.allTasksComplete) {
return (
<View>
<CongratsMsg />
<TouchableOpacity onPress={this.lastButton}>
<View style={styles.buttonDone}>
<Text style={styles.buttonText2}>
Получить
</Text>
</View>
</TouchableOpacity>
</View>
)
}
}
lastMessage() {
if (this.state.zozo) {
return (
<View style={styles.MessageBG}>
<View >
{
this.state.finishedTasks.map(task => {
return (
<View>
<Text style={styles.MessageText}>*** {task.taskText} ***</Text>
</View>
)
})
}
</View>
</View>
)
}
}
lastButton = async () => {
this.setState({ zozo: true })
}
buttonTaskOne = async () => {
this.setState({ task1: true })
try {
await AsyncStorage.setItem('task1', JSON.stringify(true))
/* firestore()
.collection('Rooms')
.doc(this.state.newRoomName)
.set({
firstVisit: false
}, { merge: true }) */
} catch (e) {
console.log(e)
}
}
buttonTaskTwo = async () => {
this.setState({ task2: true })
try {
await AsyncStorage.setItem('task2', JSON.stringify(true))
} catch (e) {
console.log(e)
}
}
buttonCongrats = () => {
this.setState({ allTasksComplete: true })
}
render() {
return (
<ImageBackground source={bgImage} style={styles.backgroundContainer}>
<ScrollView style={styles.secondBg}>
<DefaultMsg />
<TouchableOpacity onPress={this.buttonTaskOne}>
<View style={styles.buttonDone}>
<Text style={styles.buttonText2}>
Начать!
</Text>
</View>
</TouchableOpacity>
<View>
{this.taskOne()}
</View>
<View>
{this.taskTwo()}
</View>
<View>
{this.CongratsMsg()}
</View>
<View>
{this.lastMessage()}
</View>
</ScrollView>
</ImageBackground>
)
}
}
I end up saving my state in firebase instead of asyncstorage.
I'm getting the following warning in my react native app screen:
Warning: Cannot update during an existing state transition (such as within 'render'). Render functions should be a pure function of props and state.
As you see it seems to have something related with my getVolunteerDonations method.
Here´s my code:
export default class ProfileScreen extends React.Component {
constructor() {
super();
this.state = {
activeCard : null,
selectedIndex: 0,
user: null,
ongoingDonations: [],
finalizedDonations: [],
volunteerOngoingDonations: [],
deliveredDonations: [],
isLoadingDonations: true,
isLoadingDeliveries: true,
alertStatus: "",
alertTitle: "",
alertMessage: ""
};
this.getVolunteerDonations = this.getVolunteerDonations.bind(this);
this.getOngoingDonations = this.getOngoingDonations.bind(this);
this.displayDropdownAlert = this.displayDropdownAlert.bind(this);
}
loadUser(){
let userEmail = encodeURIComponent(auth().currentUser.email);
Promise.all([
fetch(API_URL + "/users?email=" + userEmail)
])
.then(([res1]) => Promise.all([res1.json()]))
.then(([data1]) => {
this.setState({
user: data1[0]
})})
.catch((error) => console.log(error))
}
componentDidMount(){
this.loadUser();
}
triggerReload (status, title, message) {
this.setState({
isLoadingDonations: true,
isLoadingDeliveries: true,
alertStatus: status,
alertTitle: title,
alertMessage: message
})
}
displayDropdownAlert(status, title, message) {
if(this.dropDownAlertRef) {
this.dropDownAlertRef.alertWithType(status, title, message);
}
}
getVolunteerDonations() {
if (this.state.user != null && this.state.user.category === "Voluntario" && this.state.isLoadingDonations === false
&& this.state.isLoadingDeliveries) {
const email = this.state.user.email.replace("+", "%2B");
return fetch(API_URL + "/donations?volunteer=" + email)
.then(response => response.json())
.then(volunteerDonations => {
let volunteerDonationsAux = [];
let volunteeerDonationsDone = [];
volunteerDonations.map(donation => {
if (donation.status === 'DELIVERED' || donation.status === 'CANCELLED-BY-USER') {
volunteeerDonationsDone.push(donation);
} else {
volunteerDonationsAux.push(donation);
}
})
this.setState({
volunteerOngoingDonations: volunteerDonationsAux,
deliveredDonations: volunteeerDonationsDone,
isLoadingDeliveries: false
});
});
} else if (this.state.user != null && this.state.user.category != "Voluntario" && this.state.isLoadingDeliveries) {
this.setState({
isLoadingDeliveries: false
});
}
}
render() {
if(this.state.alertStatus != "") {
this.displayDropdownAlert(this.state.alertStatus, this.state.alertTitle, this.state.alertMessage);
}
this.getOngoingDonations();
this.getVolunteerDonations();
let options;
let listData;
let itemType;
if (this.state.user != null) {
if (this.state.user.category == "Donante" || this.state.user.category == "Quiero ser Voluntario") {
options = ['Donaciones'];
listData = this.state.ongoingDonations;
itemType = "offer";
} else {
options = ['Entregas', 'Donaciones'];
if(this.state.selectedIndex == 0) {
listData = this.state.volunteerOngoingDonations;
itemType = "delivery";
} else {
listData = this.state.ongoingDonations;
itemType = "offer";
}
}
}
const {navigate} = this.props.navigation;
if (this.state.isLoadingDonations || this.state.isLoadingDeliveries){
return(
<ScrollView contentContainerStyle={{alignItems: "center", flex: 1, justifyContent: 'center'}}>
<Spinner isVisible={true} size={100} type={'Pulse'} color={'#013773'}/>
</ScrollView>
)
} else {
const getHeader = () => {
return <View>
<Ionicons name="ios-settings" size={40} color="#013773"
onPress={() => navigate('Configuración', {user: this.state.user, finalizedDonations: this.state.finalizedDonations, deliveredDonations: this.state.deliveredDonations})}
style={{position: "absolute", right: 10}}/>
<View style={{marginTop: 45, alignItems: "center"}}>
<View style={styles.avatarContainer}>
<Image style={styles.avatar}
source={require('../../../assets/logo.png')}/>
</View>
<Text style={styles.name}> {auth().currentUser.displayName} </Text>
</View>
<View style={styles.stat}>
<Text style={styles.statTitle}> {this.state.user.category === "Voluntario" ? "Voluntario": "Donante"} - {this.state.user.phone} </Text>
<Text style={styles.statTitle}> {this.state.user.email} - {this.state.user.address} </Text>
{this.state.user.category === "Donante" ? <Button
buttonStyle={styles.wantVolunteer}
title='Quiero ser voluntario' onPress={() => navigate("Quiero ser voluntario", {user: this.state.user, finalizedDonations: this.state.finalizedDonations, deliveredDonations: this.state.deliveredDonations})}/>
: null}
</View>
{this.renderSummary()}
{options.length > 1 ?
<SegmentedControlTab
values={options}
selectedIndex={this.state.selectedIndex}
onTabPress={this.handleIndexChange}
tabsContainerStyle={styles.tabsContainerStyle}
tabStyle={styles.tabStyle}
tabTextStyle={{fontSize: 18, color: '#013773'}}
activeTabStyle={{ backgroundColor: '#013773' }}
/> :
null}
</View>
}
const getFooter = () => {
if(listData.length <= 0){
return <View style={{alignItems: 'center'}}>
<Text style={styles.nostuff}> ¡Nada para mostrar! </Text>
</View>;
}
return null;
}
return (
<View>
<FlatList
data={listData}
renderItem={({ item }) => <ProfileItem itemType={itemType} item={item} volunteer="test" triggerAlert={this.displayDropdownAlert.bind(this)} updateParentState={this.triggerReload.bind(this)}/>}
keyExtractor={(item, index) => index.toString()}
ListHeaderComponent={getHeader}
ListFooterComponent={getFooter}
onRefresh={() => this.triggerReload("", "", "")}
refreshing={this.state.isLoadingDonations}
/>
<DropdownAlert ref={ref => this.dropDownAlertRef = ref} />
</View>
);
}
}
}
Sorry for the long code but I'm not sure what might be relevant or not. I tried to remove some functions to make it a little bit shorter.
As you see I call getVolunteerDonations inside my render method.
I think the issue may be that you are calling the getVolunteerDonations function within your render, but that function does setState. Whenever you setState, you re-render so it's a bit of a vicious cycle.
Are you sure you need to call the function within render?
I suggest you call the getVolunteerDonations function within componentDidMount or if you rely on the loadUser call first, then call getVolunteerDonations within a .then() of the loadUser function.
Been working on finding a way to delete the clicked on document using React Native and Cloud Firestore. I can't figure out a way to get the document id and then use it in my code to replace the value of deleteItemId. Any ideas?
My collection with a document showing:
My code:
componentDidMount(){
this.getItems();
const { currentUser } = firebase.auth();
this.setState({ currentUser });
}
getItems = async () => {
this.setState({ refreshing: true });
this.unsubscribe = await this.ref.onSnapshot((querySnapshot) => {
const todos = [];
querySnapshot.forEach((doc) => {
todos.push({
tips: doc.data().tips,
date: doc.data().date,
user: doc.data().user,
like: doc.data().like
})
})
this.setState({
refreshing: false,
getData: todos
})
})
}
deletePost = () => {
const deleteItemId = "SELECTED DOCUEMNT ID HERE";
firestore.collection("tips").doc(deleteItemId).delete().then(function() {
alert("deleted")
}).catch(function(error) {
alert("Error removing document: ", error);
});
}
renderItem = ({ item, index }) => {
let date = item.date;
return (
<View style={styles.tips}>
<View style={styles.wrapper}>
<View style={styles.profilePicture}>
<View></View>
</View>
<View style={styles.right}>
<Text style={styles.username}>#{item.user}</Text>
<Text style={styles.date}>{ moment(item.date).fromNow() }</Text>
</View>
</View>
<Text style={styles.text}>{item.tips}</Text>
<View style={styles.bar}>
<Text><Icon onPress={() => this.like()} style={styles.heart} type="Octicons" name="heart" /> {item.like}</Text>
<Text onPress={() => {
this.setModalVisible(true);
}}><Icon style={styles.comment} type="FontAwesome" name="comment-o" /> {item.replies}</Text>
<Text onPress={() => this.deletePost()}><Icon style={styles.settings} type="Octicons" name="kebab-vertical" /></Text>
</View>
</View>
)
}
Every time you push a TODO to todos, make sure to also include the document ID:
todos.push({
id: doc.id,
tips: doc.data().tips,
date: doc.data().date,
user: doc.data().user,
like: doc.data().like
})
Then when you render a TODO, you include the ID in the rendering output of the eleent:
<Text onPress={() => this.deletePost(styles.id)}>
I'm mapping an array to be rendered in React Native. On an event (button press) I want to add and object to the array and it be rendered on the screen. I am getting the lifecycle functions to trigger, but not sure if they are needed for this or how to utilize them effectively. Any help would be greatly appreciated!
class Home extends Component {
constructor(props) {
super(props)
this.state = {
data: '',
text: '',
submitted: false,
count: 1,
arr: [
{
key: 1,
text: "text1"
},
],
}
buttonsListArr = this.state.arr.map(info => {
return (
<View style={styles.container}>
<Text key={info.key}>{info.text}</Text>
<Button title='Touch' onPress={() => {
this.setState({count: this.state.count + 1})
}}/>
</View> )
})
}
shouldComponentUpdate = () => {
return true;
}
componentWillUpdate = () => {
this.state.arr.push({key: this.state.count, text: 'text2'})
}
render() {
return (
<View style={styles.container}>
{buttonsListArr}
</View>
)
}
}
What you've written is not typical. A quick fix is to move the logic to a render function like
constructor(props) {
.
.
this.renderText = this.renderText.bind(this)
}
renderText() {
return this.state.arr.map(info => {
return (
<View style={styles.container}>
<Text key={info.key}>{info.text}</Text>
<Button title='Touch' onPress={() => {
this.setState({count: this.state.count + 1})
}}/>
</View> )
})
}
then call the function within the render()
render() {
return (
<View style={styles.container}>
{this.renderText()}
</View>
)
}
You shouldn't be using a lifecycle call to add an element to an array. Simply call setState and it will rerender itself!
Try this.
<View style={styles.container}>
<Text key={info.key}>{info.text}</Text>
<Button title='Touch' onPress={() => {
this.setState({
count: this.state.count + 1
arr: this.state.arr.push({key: this.state.count, text: 'text2'})
})
}}/>
</View> )
return this.arrayholder.map(image => {
return (
<View style={styles.ImageContainer}>
<Image style={styles.ImageContainer} source={image} />
</View>
)
})
I am working on a simple to do application linked to firebase using react native. i have one class with a few methods in it,as far as I can tell by searching online about this problem, it seems to be something related to an output in the render function. but i checked my methods and I am unable to pin down the exact problem.
and this is my class:
class ToDo_React extends Component {
constructor(props) {
super(props);
var myFirebaseRef = new Firebase(' ');
this.itemsRef = myFirebaseRef.child('items');
this.state = {
newTodo: '',
todoSource: new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2})
};
this.items = [];
}
componentDidMount() {
// When a todo is added
this.itemsRef.on('child_added', (dataSnapshot) => {
this.items.push({id: dataSnapshot.key(), text: dataSnapshot.val()});
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
this.itemsRef.on('child_removed', (dataSnapshot) => {
this.items = this.items.filter((x) => x.id !== dataSnapshot.key());
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
}
addTodo() {
if (this.state.newTodo !== '') {
this.itemsRef.push({
todo: this.state.newTodo
});
this.setState({
newTodo : ''
});
}
}
removeTodo(rowData) {
this.itemsRef.child(rowData.id).remove();
}
render() { return (
<View style={styles.appContainer}>
<View style={styles.titleView}>
<Text style={styles.titleText}>
My Todos
</Text>
</View>
<View style={styles.inputcontainer}>
<TextInput style={styles.input} onChangeText={(text) => this.setState({newTodo: text})} value={this.state.newTodo}/>
<TouchableHighlight
style={styles.button}
onPress={() => this.addTodo()}
underlayColor='#dddddd'>
<Text style={styles.btnText}>Add!</Text>
</TouchableHighlight>
</View>
<ListView
dataSource={this.state.todoSource}
renderRow={this.renderRow.bind(this)} />
</View>
);
}
renderRow(rowData) {
return (
<TouchableHighlight
underlayColor='#dddddd'
onPress={() => this.removeTodo(rowData)}>
<View>
<View style={styles.row}>
<Text style={styles.todoText}>{rowData.text}</Text>
</View>
<View style={styles.separator} />
</View>
</TouchableHighlight>
);
}
}
The issue is with your renderRow method where you render:
<Text style={styles.todoText}>{rowData.text}</Text>
Here you are passing an object as a children of Text element instead of a string. That is because you're setting an object under the text key for your data store here:
this.items.push({id: dataSnapshot.key(), text: dataSnapshot.val()});
Note that val() here is a reference to an object you've pushed to your firebase instance here (see firebase docs):
this.itemsRef.push({
todo: this.state.newTodo
});
So what you perhaps want to do here is to just push this.state.newTodo instead of an object.