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.
Related
As title says, I need to create a modal popup for every list item for the selected sizes to be displayed. I'm using the react-native-popup-dialog but without results. Could you help me? Here's my code:
step1 = () => {
return (
<View style={{ flex: 8}}>
<Text h3 style={{ ...styles.title, marginVertical: 10.0 }}>{this.state.user=="User"?"Size":"CargoSize"}</Text>
<ScrollView>
<View style={{ marginHorizontal: 16.0 }}>{
this.state.size.map((l, i) => (
<ListItem key={i} onPress={() => this.setState({sizeSelected: i, sizeName: l.title, sizeId: l.id})} underlayColor='transparent'
containerStyle={{backgroundColor: this.state.sizeSelected==i?'#F76858':'white', borderWidth: 1.0,
borderColor: '#707070', marginBottom: 10.0, paddingVertical: 5.0, paddingHorizontal: 40.0}}>
<ListItem.Content>
<View style={{
flexDirection: 'row', alignItems: 'center',
justifyContent: 'center'
}}>
<Text style={styles.textSize}>{l.title}</Text>
<Text style={{ fontSize: 16 }}>{l.example}</Text>
</View>
</ListItem.Content>
</ListItem>
))
}</View>
</ScrollView>
</View>
);
}
I believe you can declare a single modal in your list component and show or hide the modal when the user presses the listItem. Also, make the pressed item active and pass the item to your modal so that the modal can show the details of the active item.
App.js
export default function App() {
const [modalVisible, setModalVisible] = React.useState(false);
const [activeItem, setActiveItem] = React.useState(null);
const onPress = (item) => {
setActiveItem(item)
setModalVisible(true)
}
const renderItem = ({ item }) => (
<TouchableOpacity onPress={()=>onPress(item)}>
<Item title={item.title} />
</TouchableOpacity>
);
return (
<View style={styles.container}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
<Popup modalVisible={modalVisible} setModalVisible={setModalVisible} activeItem={activeItem} />
</View>
);
}
Modal.js
export default function Popup({modalVisible, setModalVisible, activeItem}) {
return (
<View style={styles.centeredView}>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Text style={styles.modalText}>{activeItem?.title}</Text>
<TouchableHighlight
style={{ ...styles.openButton, backgroundColor: '#2196F3' }}
onPress={() => {
setModalVisible(!modalVisible);
}}>
<Text style={styles.textStyle}>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
</View>
);
}
Here is a working demo for you.
I made something similar in my react-native project and do this:
Made with {useState} the variables i need in my modal, example, the name of my component
Wrap the ListItem component in a Pressable component
Put the onPress={() => {//Put your code here}} function, inside you will put the SetVariable of the variable you want in the modal
The Pressable will update the variable with the wich you want in the modal
Right so I have a sports app and im just displaying the fixtures for each team like so:
export const Fixtures = ({ fixtures }) => {
console.log('rendering again')
return (
<View>
<Text style={{ fontSize: 17, alignSelf: 'center', fontWeight: '700', marginBottom: 10 }}>
Gameweek Fixtures
</Text>
<View style={styles.container}>
<View>
{fixtures.map((match: any) => (
<View key={match.home} style={styles.match}>
// displaying fixtures here
</View>
))}
</View>
</View>
</View>
)
}
I'm displaying the images for the logo for each team above and I was noticing a flicker and when ever this component renders. and it renders in a top nav view. i.e. I click on the fixtures in the navbar and it shows this component.
I don't like the flicker so I'm trying to make it more performant and decided to use react.memo
I wrapped the above in React.memo like so:
export const Fixtures = React.memo(({ fixtures }) => {
console.log('rendering again')
return (
<View>
<Text style={{ fontSize: 17, alignSelf: 'center', fontWeight: '700', marginBottom: 10 }}>
Gameweek Fixtures
</Text>
<View style={styles.container}>
<View>
{fixtures.map((match: any) => (
<View key={match.home} style={styles.match}>
// displaying fixtures here
</View>
))}
</View>
</View>
</View>
)
},
(prevProps, nextProps) => {
console.log(prevProps, nextProps, 'the propss')
return true // props are not equal -> update the component)
})
but for some reason it is not logging the console.log when the component renders but it does log the line at the top that says: console.log('rendering again').
why is it not logging? and what can i do to prevent this flicker on every render? I just want to display the fixtures, instead it flashes, flickers then shows them (might be ok on first render for this but not on every subsequent one)
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.
So, I'm using 2 files: The components.js where I define all parts and molds I'll use in my app, and the App.js, which has the logic and major stuff.
To sum things up, I've defined a Card class in the components.js that has a button at the end. Here is the code for the Card:
export class Card extends Component {
render() {
return (
<View style={{height: 700}}>
<View style={{flex: 8, backgroundColor: 'white', borderRadius: 20}} >
<Image source={{uri: this.props.pic}} style={styles.img}/>
<Text style={{flex: 1, fontWeight: 'bold',fontSize: 30, paddingTop: 10}}> {this.props.nome}</Text>
<Text style={{flex: 1, fontSize: 20}}> {this.props.descricao}</Text>
<TouchableHighlight style={{flex: 1}} onPress={() => ???????} underlayColor="white">
<View style={styles.button}>
<Text style={styles.buttonText}>Mais informações</Text>
</View>
</TouchableHighlight>
</View>
</View>
);
}
}
And I use it in the App.js as follows:
import {Card} from "./components.js";
class App extends Component {
render() {
return (
<View style={{flex: 1, backgroundColor:"#e6e6e1"}}>
<ScrollView>
{this.state.locations.map((pico, key) =>
<Card key={key} pic={pico.img[0]} nome= {pico.nome_do_pico} descricao={pico.how}/>
)}
</ScrollView>
</View>
);
}
}
(I've omitted the parts where it gets the info from the server and puts into the locations array)
What I need is for the button to navigate to another screen, called "Details", and I have tried a few things on the onPress but nothing has worked so far. The documentation for reactnavigation uses this.state.navigation.navigate("Details") as example, but since the Card is in another file it can't access the this.state.navigation. Any ideas?
Try if you can solve the problem by modifying the following part of your code:
in app.js
<Card key={key} pic={pico.img[0]} nome= {pico.nome_do_pico} descricao={pico.how} navigation={navigation}/>
in components.js:
<TouchableHighlight style={{flex: 1}} onPress={() => navigation.navigate('Details', {YourParameters: xx } } underlayColor="white">
<View style={styles.button}>
<Text style={styles.buttonText}>Mais informações</Text>
</View>
</TouchableHighlight>