I need your help. I have a problem with updating data on a page. Basically I have a homepage where there is data like "FirstName: ...", "LastName: ..." which are retrieved from the login.
Once the login has been completed, you will automatically be taken to the Homepage page each time the app is started.
In this way I retrieve the information on the Homepage page.
The problem is that the user can modify this data through a form (ModifyProfile), and once the data is done they are not updated.
How can I update them anyway?
Thank you.
Homepage.js
class HomepageUtente extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
const FirstName = global.utente.data.Person.FirstName;
const LastName = global.utente.data.Person.LastName;
return (
<View style={style.container}>
<View style={style.page}>
<Icon name="user-circle" color="#64c7c0" size={70} onPress={() => Actions.yourprofile({ cf: Username } )} />
<Text
style={{ textAlign: 'center', fontSize: 20, }}>{"Welcome"}
</Text>
<Text style={{ textAlign: 'center', fontSize: 20, color: '#64c7c0', fontWeight: 'bold' }}>
{FirstName} {LastName}
</Text>
<Text style={{ color: '#64c7c0', paddingTop: 20 }} onPress={() => Actions.ModifyProfile({ cf: Username })} >Modify Profile</Text>}
</View>
</View>
)
}
}
ModifyProfile
export default class ModificaProfilo extends Component {
constructor(props) {
super(props);
this.state = {};
}
findUtente(cf) {
//Search data cf in the db
//......
//......
.then(response => {
let utente = response.docs[0];
console.log("Utente: " + utente)
console.log("Sei qui 1")
utente.Person.FirstName = this.state.FirstName;
utente.Person.LastName = this.state.LastName;
global.utente.db.localdb().put(utente);
})
.catch(function(err) {
console.log(JSON.stringify(err));
})
}
render() {
return (
<View style={style.container}>
<View style={style.page}>
<KeyboardAwareScrollView>
<View style={style.inputContainer}>
<TextInput
style={style.inputs}
placeholder="Name"
placeholderTextColor="#64c7c0"
keyboardType="default"
underlineColorAndroid="grey"
onChangeText={FirstName =>
this.setState({ FirstName })
}
/>
</View>
<View style={style.inputContainer}>
<TextInput
style={style.inputs}
placeholder="Surname"
placeholderTextColor="#64c7c0"
keyboardType="default"
underlineColorAndroid="grey"
onChangeText={LastName =>
this.setState({ LastName })}
/>
</View>
<View style={style.footer}>
<TouchableOpacity
style={[style.button, style.buttonOK]}
onPress={() => this.findUtente(this.props.cf)}
>
<Text style={style.buttonTesto}>Modifica</Text>
</TouchableOpacity>
</View>
Actually, you can do that but you shouldn't. Let's solve it first.
The component in Homepage.js, we call it A ;
The component in ModifyProfile, we call it B ;
You need a reference to component A, and then call A.forceUpdate().
It means you add global.A = this in Homepage.js;
add global.A.forceUpdate() in ModifyProfile after you get the new data;
Why: React Component would reRender only if the state or props of the component changes, that's why you need to call forceUpdate to make component A reRender again unconditionally.
if your change the FirstName by global.utente.data.Person.FirstName = 'NewName', component A can not detect the change event.
By the way, you should use a state container like redux to help you, rather than a global variable. You can connect FirstName as your props.
I recommend dvajs which is easy to learn, you can just focus on the data and flow, you don't need to care about if a component should update most of the times.
And there is a starter of dvajs, you can just run it quickly followed by:
react-native-dva-starter
Forget it if I misunderstood your question.
Related
Dynamically generate a TextInput when you press a button but I can’t get the value that the user digits,try to use states but I can’t because it’s not with the other general textInputs but it’s imported as Field.
try to create a state in the component file and move it to the general view and print it to see if it works and not...is there any way to bring this state?
general view:
import Campo from './campoInput';
constructor(props){
super(props);
this.state={
Cbusto:"",
Ccintura:"",
Ccadera:"",
valueArray: []
};
this.addNewEle = false;
}
agregarCampo=()=>{
this.addNewEle = true;
const newlyAddedValue = { text: 'prueba'};
this.setState({
valueArray: [...this.state.valueArray, newlyAddedValue]
});
}
render(){
return(
------Here are the normal textInput-----
<View style={{ flex: 1, padding: 4 }}>
{this.state.valueArray.map((ele) => {
return <Campo item={ele} />;
})}
</View>
<View style={styles.flex}>
<View style={styles.ButtonAdd}>
<Button
title="Add input"
color="#B13682"
onPress={this.agregarCampo}
></Button>
</View>
)
}
Component:
constructor(props){
super(props);
this.state={
info:""
};
}
render(){
return(
<View>
<Text>pruba:{this.props.item.text}</Text>
<View style={styles.input}>
<TextInput onChangeText={(text) => this.setState({info:text})}></TextInput>
</View>
</View>
)
}
can be solved by adding the onChangeText event to the Field component in the overview and in the same way in the TextInput of the component being imported, using props status
General view:
<View style={{ flex: 1, padding: 4 }}>
{this.state.valueArray.map((ele) => {
return <Campo item={ele}
onChangeText={(text) => this.setState({ info: text })}
/>;
})}
</View>
Component
<View>
<Text>pruba:{this.props.item.text}</Text>
<View style={styles.input}>
<TextInput onChangeText={this.props.onChangeText}></TextInput>
</View>
</View>
I have a flat list, which gets its data source as a state. Actually, this data is from firebase, and i have been using redux. So, the data is fetched in the actions, and using callback i get the data to state.
What i want to achieve is, when there is no data found from the api, An empty list message should be show in the view. Actually , i achieved this using "ListEmptyComponent". But whats happening is the screen starts with empty message, and the spinner loads below it, and then if data found the message goes away as well as spinner.
But, what i wanted is, when the view gets rendered the first thing everyone should see is the spinner, and then if data empty spinner hides then empty list message displays.
How to achieve this ?
My Action :
export const fetchOrderHistory = (phone, callback) => {
return (dispatch) => {
dispatch({ type: START_SPINNER_ACTION_FOR_ORDER_HISTORY })
firebase.database().ref('orders/'+phone)
.on('value', snapshot => {
const snapShotValue = snapshot.val();
callback(snapShotValue);
dispatch ({ type: ORDER_HISTORY_FETCHED , payload: snapshot.val()});
dispatch({ type: STOP_SPINNER_ACTION_FRO_ORDER_HISTORY })
});
};
};
My Flat List & spinner:
<FlatList
data={this.state.historyOfOrders}
keyExtractor={item => item.uid}
ListEmptyComponent={this.onListEmpty()}
renderItem={({ item }) => (
<Card
containerStyle={{ borderRadius: 5 }}
>
<View style={styles.topContainerStyle}>
<View>
<TouchableOpacity
onPress={() => this.props.navigation.navigate('ViewOrderScreen', {itemsOfOrder: item}) }
>
<View style={styles.viewOrderContainer}>
<View style={styles.viewOrderTextContainer}>
<Text style={styles.viewOrderTextStyle}>View Order</Text>
</View>
<Icon
name='ios-arrow-forward'
type='ionicon'
color='#ff7675'
/>
</View>
</TouchableOpacity>
</View>
</View>
</View>
</Card>
)}
/>
{this.props.isSpinnerLoading &&
<View style={styles.loading}>
<ActivityIndicator size="large" color="#03A9F4"/>
</View> }
My Call back at componentWillMount which set state:
componentWillMount() {
this.props.fetchOrderHistory((this.props.phone), (snapShotValue)=> {
const userOrderHistory = _.map(snapShotValue, (val,uid) => ({uid, ...val}))
this.setState({ historyOfOrders: userOrderHistory })
});
}
My EmptyList Message:
onListEmpty = () => {
return <View style={{ alignSelf: 'center' }}>
<Text style={{ fontWeight: 'bold', fontSize: 25 }}>No Data</Text>
</View>
}
My State:
state = { historyOfOrders: "" }
I am getting the spinner values from the reducers, using mapStateToProps.
Kindly Guide me, through
you have to do two things for that.
First, show Flatlist only if the loader is stopped. Second, set default value of this.state.historyOfOrders is null and check if this.state.historyOfOrders not null then only show Flatlist.
Here is a code:
{(!this.props.isSpinnerLoading && this.state.historyOfOrders != null) ?
(
<FlatList
data={this.state.historyOfOrders}
keyExtractor={item => item.uid}
ListEmptyComponent={this.onListEmpty()}
renderItem={({ item }) => (
<Card containerStyle={{ borderRadius: 5 }}>
<View style={styles.topContainerStyle}>
<View>
<TouchableOpacity onPress={() => this.props.navigation.navigate('ViewOrderScreen', {itemsOfOrder: item}) }>
<View style={styles.viewOrderContainer}>
<View style={styles.viewOrderTextContainer}>
<Text style={styles.viewOrderTextStyle}>View Order</Text>
</View>
<Icon
name='ios-arrow-forward'
type='ionicon'
color='#ff7675'
/>
</View>
</TouchableOpacity>
</View>
</View>
</Card>
)}
/>
) : null
}
With this condition, even if you want loader above Flatlist you can do that.
The path you should take is rendering only the spinner when the loading flag is set and rendering the list when loading flag is false.
Your render method should be like below
render()
{
if(this.props.isSpinnerLoading)
{
return (<View style={styles.loading}>
<ActivityIndicator size="large" color="#03A9F4"/>
</View> );
}
return (/** Actual List code here **/);
}
So what I do now: I am using Firebase for my React Native project.
The user writes another user's email in . Then there is a check if this email exists in database. If the email exists, then it writes as an array to users=[] in state and should be rendered in FlatList.
Everything works fine except one thing: FlatList doesn't render anything though I have an array.
That's what I receive from console.log(users):
[{...}]
0:
email: "test#gmail.com"
id: "-LSVedv_anPyD3We-4_Q"
That's how my code looks like:
state = {
promptVisible: false,
loading: false,
users: []
};
findUserEmail = (email) => {
firebase.database()
.ref(`/users`)
.orderByChild("email")
.equalTo(email)
.once("value")
.then(snapshot => {
if (snapshot.val()) {
const value = snapshot.val()
this.setState({ users: Object.keys(value).map((id) => ({
id,
...value[id]
})), promptVisible: false})
} else {
Alert.alert("Email doesn't exist")
}
})
}
renderItem({item}) {
return (
<View style = {styles.contactContainer}>
<View style={styles.userRow}>
<View style={styles.userImage}>
<Avatar
width={60}
rounded
source={{
uri: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/60/Matterhorn_from_Domhütte_-_2.jpg/1200px-Matterhorn_from_Domhütte_-_2.jpg"
}}
/>
</View>
<View>
<View style={styles.emailBackground}>
<Text
style = {styles.contact} >
{item.email}
</Text>
</View>
<Text
style = {styles.message} >
Da inne lauft öpis...
</Text>
</View>
</View>
</View>
)
}
render() {
console.log(this.state.users);
if (this.state.loading) {
return (
<View style={{alignItems: 'center', justifyContent: 'center', flex: 1}}>
<ActivityIndicator size="large" color="dodgerblue" />
</View>
)
}
return (
<View style={styles.container}>
<Header
centerComponent={{ text: 'Nachrichten', style: { color: '#FF0000', fontSize: 20, fontWeight: 'bold' } }}
outerContainerStyles={{ backgroundColor: '#ffffff' }}
rightComponent={this.renderButton()}
/>
<View style = {styles.contentContainer}>
<FlatList
data={this.state.users}
renderItem={this.renderItem}
keyExtractor={item => item.email}
/>
</View>
<Prompt
title="Email eingeben"
placeholder="Email"
visible={this.state.promptVisible}
onCancel={() => this.setState({promptVisible: false})}
onSubmit={(email) => this.findUserEmail(email)} />
</View>
);
}
So the question is what do I do wrong? And how can I fix it?
So I found an error. That was too simple and dumb in the same way.
I just deleted this component in render and everything worked fine.
<View style = {styles.contentContainer}>
Try replacing this...
<FlatList
data={this.state.users}
renderItem={this.renderItem}
keyExtractor={item => item.email}
/>
With a static, object-filled array in the shape of what you expect this.state.users to look like. Here's an example...
<FlatList
data={[{id: "A", email: "testA#gmail.com"},{id: "B", email: "testB#gmail.com"}]}
renderItem={this.renderItem}
keyExtractor={item => item.email}
/>
This won't solve your problem, but it's what I was suggesting as a troubleshooting step.
I'm still learning ReactJS / React Native and I'm stuck with a stupid thing I'm sure. Here's my case: I want to receive data in my child component and display it in a Modal. So:
I have a function like this (axios, API, ...):
getProductInfo = (product_id) => {
axios.get(
`API-EXAMPLE`
)
.then((response) => {
this.setState({
isVisible: false,
productInfo: response.data
})
console.log(this.state.productInfo);
})
}
I pass the function to my Child Component with the "onModalPress":
<CatalogList productsList={this.state.displayProducts} onModalPress={this.getProductInfo}/>
And here, some info about the Child Component:
const CatalogList = ({productsList, onModalPress}) => (
<Card containerStyle={styles.container}>
<View style={{ padding:20, margin:0, flexDirection: 'row', flexWrap: 'wrap', flex: 1, justifyContent: 'space-between' }}>
{
productsList.map((p, i) => {
return (
<TouchableHighlight key={i} onPress={() => onModalPress(p.id)}>
<View style={style.card}>
<View style={style.content}>
<View style={{width: 170, zIndex: 2}}>
<Text style={style.name}>{p.id}</Text>
<Text style={style.name}>{p.name}</Text>
<Text style={style.winemaker}>Domaine : {p.domain}</Text>
<Text style={style.winemaker}>Origine : {p.wine_origin}</Text>
<Text style={style.aop}>Appellation : {p.appellation}</Text>
</View>
<Image
style={style.image}
source={{ uri: p.image, width: 140, height: 225, }}
/>
</View>
<View style={style.entitled}>
<Text style={[style.priceText, style.cadetGrey]}>{p.publicPriceText}</Text>
<Text style={style.priceText}>{p.subscriberPriceText}</Text>
</View>
<View style={style.row}>
<Text style={[style.price, style.cadetGrey]}>{p.price} €</Text>
<Text style={style.price}>{p.subscriber_price} €</Text>
</View>
<View style={[{backgroundColor: p.label_colour}, style.label]}>
<Text style={style.labelText}>{p.label}</Text>
</View>
<Modal isVisible={false}>
<View style={{ flex: 1 }}>
{/* <Text>{productInfo.rewiew_wine_waiter}</Text> */}
</View>
</Modal>
</View>
</TouchableHighlight>
);
})
}
</View>
</Card>
);
The "p.id" comes from another data (productList) that I get with another Axios API Call. With "p.id" I get the product_id I need in my function
getProductInfo
Everything works and I display the info inside my console.log (this.state.productInfo).
My issue and I think is easy... It's how can I "store/stock" this info I have in the console.log in a const/props to use it in my Modal and call it like in this example:
<Modal isVisible={false}>
<View style={{ flex: 1 }}>
<Text>{productInfo.rewiew_wine_waiter}</Text>
</View>
</Modal>
Of course, any other advice is welcome!
React is all about one-way data flow down the component hierarchy
Let's assume that you have a Container component that fetch all the data:
class MyContainer extends Component{
state = {
myItensToDisplay: []
}
componentDidMount(){
//axios request
.then(res => this.setState({myItensToDisplay: res.itens}))
}
}
Looking good! Now you have all the data you want to display fetched and stored in your container's state. Let's pass it to a Itemcomponent:
class MyContainer extends Component{
// All the code from above
render(){
const itens = this.state.myDataToDisplay.map( item =>{
return(<Item name={item.name} price={item.price} />);
})
return(
<div>
{itens}
</div>
)
}
}
Now you are fetching all the data you want to display in a parent component and distributing that data to it's childrens via props.
I have a View that contains a button - onPress it opens a modal showing a list of contacts.
onPress of any of those contacts (pickContact function) I would like to dynamically add a new View to _renderAddFriendTile (above button).
Also ideally, the 'Add' icon next each contact name (in the modal) should update ('Remove' icon) whether or not they are present in _renderAddFriendTile View.
What would be the best way to do it?
[UPDATED code]
import React, {Component} from 'react'
import {
Text,
View,
ListView,
ScrollView,
StyleSheet,
Image,
TouchableHighlight,
TextInput,
Modal,
} from 'react-native'
const friends = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
}).cloneWithRows([
{
id: 1,
firstname: 'name01',
surname: 'surname01',
image: require('../images/friends/avatar-friend-01.png')
},
{
id: 2,
firstname: 'name02',
surname: 'surname02',
image: require('../images/friends/avatar-friend-02.png')
},
{
id: 3,
firstname: 'name03',
surname: 'surname03',
image: require('../images/friends/avatar-friend-03.png')
},
{
id: 4,
firstname: 'name04',
surname: 'surname04',
image: require('../images/friends/avatar-friend-04.png')
},
])
class AppView extends Component {
state = {
isModalVisible: false,
contactPicked: [],
isFriendAdded: false,
}
setModalVisible = visible => {
this.setState({isModalVisible: visible})
}
pickContact = (friend) => {
if(this.state.contactPicked.indexOf(friend) < 0){
this.setState({
contactPicked: [ ...this.state.contactPicked, friend ],
})
}
if(this.state.contactPicked.indexOf(friend) >= 0){
this.setState({isFriendAdded: true})
}
}
_renderAddFriendTile = () => {
return(
<View style={{flex: 1}}>
<View style={[styles.step, styles.stepAddFriend]}>
<TouchableHighlight style={styles.addFriendButtonContainer} onPress={() => {this.setModalVisible(true)}}>
<View style={styles.addFriendButton}>
<Text style={styles.addFriendButtonText}>Add a friend</Text>
</View>
</TouchableHighlight>
</View>
</View>
)
}
render(){
return (
<ScrollView style={styles.container}>
<Modal
animationType={'fade'}
transparent={true}
visible={this.state.isModalVisible}
>
<View style={styles.addFriendModalContainer}>
<View style={styles.addFriendModal}>
<TouchableHighlight onPress={() => {this.setModalVisible(false)}}>
<View>
<Text style={{textAlign:'right'}}>Close</Text>
</View>
</TouchableHighlight>
<ListView
dataSource={friends}
renderRow={(friend) => {
return (
<TouchableHighlight onPress={() => {this.pickContact()}}>
<View style={[styles.row, styles.friendRow]}>
<Image source={friend.image} style={styles.friendIcon}></Image>
<Text style={styles.name}>{friend.firstname} </Text>
<Text style={styles.name}>{friend.surname}</Text>
<View style={styles.pickContainer}>
<View style={styles.pickWrapper}>
<View style={this.state.isFriendAdded ? [styles.buttonActive,styles.buttonSmall]: [styles.buttonInactive,styles.buttonSmall]}>
<Image source={this.state.isFriendAdded ? require('../images/button-active.png'): require('../images/button-inactive.png')} style={styles.buttonIcon}></Image>
</View>
</View>
</View>
</View>
</TouchableHighlight>
)
}}
/>
</View>
</View>
</Modal>
{this._renderAddFriendTile()}
</ScrollView>
)
}
}
export default AppView
Since you need to update something dynamically, that's a clear indication you need to make use of local state for modelling that data. (Setting aside you are not using a state library management like Redux)
state = {
isModalVisible: false,
contactPicked: null,
}
Your pickContact function needs the friend data from the listView, so you need to call it with the row selected:
<TouchableHighlight onPress={() => {this.pickContact(friend)}}>
Then inside your pickContact function, update your UI with the new contactsPicked data model
pickContact = (friend) => {
this.setState({
contactPicked: friend,
});
}
That will make your component re-render and you can place some logic inside _renderAddFriendTile to render some extra UI (Views, Texts..) given the existence of value on this.state.contactPicked You could use something along these lines:
_renderAddFriendTile = () => {
return(
<View style={{flex: 1}}>
{this.state.contactPicked && (
<View>
<Text>{contactPicked.firstName}<Text>
</View>
)}
<View style={[styles.step, styles.stepAddFriend]}>
<TouchableHighlight style={styles.addFriendButtonContainer} onPress={() => {this.setModalVisible(true)}}>
<View style={styles.addFriendButton}>
<Text style={styles.addFriendButtonText}>Add a friend</Text>
</View>
</TouchableHighlight>
</View>
</View>
)
}
Notice you now hold in state the firstName of the contact picked, so point number 2 should be easy to address. Just inside renderRow of ListView, render a different Icon if friend.firstname === this.state.contactPicked.firstname
Note: Don't rely on firstname for this sort of check since you can have repeated ones and that would fail. The best solution is to provide to your list model an unique id property per contact and use that id property for checking the logic above.
Side Notes
The part {this.state.contactPicked && (<View><Text>Some text</Text></View)} is using conditional rendering. The && acts as an if so if this.state.contactPicked is truthy, it will render the view, otherwise it will render nothing (falsy and null values are interpreted by react as "nothing to render").
Of course, if you want to render more than one item dynamically, your state model should be an array instead, i.e contactsPicked, being empty initially. Every time you pick a contact you'll add a new contact object to the array. Then inside _renderAddFriendTile you can use map to dynamically render multiple components. I think you won't need a ListView, unless you wanna have a separate scrollable list.
_renderAddFriendTile = () => {
return(
<View style={{flex: 1}}>
{this.state.contactsPicked.length && (
<View>
{this.state.contactsPicked.map(contact => (
<Text>{contact.firstName}</Text>
)}
</View>
)}
<View style={[styles.step, styles.stepAddFriend]}>
<TouchableHighlight style={styles.addFriendButtonContainer} onPress={() => {this.setModalVisible(true)}}>
<View style={styles.addFriendButton}>
<Text style={styles.addFriendButtonText}>Add a friend</Text>
</View>
</TouchableHighlight>
</View>
</View>
)
}