firstly, this is what is given to me from designer http://www.giphy.com/gifs/hSRrqF5ObsbXH27V09
Basically, there is a category which is passed from previous screen. and with some ui interactions, i need to render this screen again and again. the flow is like that: you select a category, if it has subCategories, let user select one of those subCategories before rendering input components. i can make it work with if and else clauses but i feel that this is some how not best practice at all. I just need an advice from experieced developer(i am reletively new to react native.)
So before writing any code with native way, i just want to ask it here so maybe i can learn more about it.
Here is my Screen:
<NewAdHoc
contentText={'Kategori Secimi'}
onBackPress={this.handleBackPress}
showContentText={false}
>
<View style={styles.container}>
{currentCategory !== null
? (
<View style={{ ...styles.flatListContainer, paddingLeft: undefined, paddingRight: undefined }}>
<View style={{ marginHorizontal: 20 }}>
<ListViewItem
categoryName={currentCategory.categoryName}
iconName={currentCategory.categoryIcon}
showBorder={false}
key={currentCategory.categoryId}
categoryId={currentCategory.categoryId}
inNewAdScreen={false}
/>
</View>
<Seperator
backgroundColor={colors.SEPERATOR_BCK}
text={'Ilan Turu'}
style={{ paddingHorizontal: 20 }}
/>
{
currentCategory.subCategories.map((subc) => (
<View style={{ marginHorizontal: 20 }}>
<SubCategoryItem
text={subc.subCategoryName}
key={subc.subCategoryId}
showBorder={true}
/>
</View>
))
}
</View>
) : null}
</View>
</NewAdHoc>
right now, i am rendering a category, a <Seperator/> between category and subcategories, and subcategories. what i want is that, when user click on one of the subCategories, i will change the state to isSubCategorySelected = true, selectedSubCategory= subCategoryId and then need to render the whole screen like in gif i provided above.
the For those who came here for answer:
What i did is basically divide and conquer paradigm. I firstly divide my sitution into two main state.
RenderInitialForm(),
renderAfterSubCategorySelected(),
those two rendering functions handle the whole process. when a TouchableOpacity is clicked, i setState with two variables: isSubCategorySelected = true, selectedSubCategory = subCategory
and in my main render() function:
render() {
const { currentCategory, isSubCategorySelected, selectedSubCategory } = this.state
return (
<NewAdHoc
contentText={'Kategori Secimi'}
onBackPress={this.handleBackPress}
showContentText={false}
>
<View style={styles.container}>
{currentCategory !== null
? isSubCategorySelected
? this.renderAfterSubCategorySelected()
: this.renderInitialForm()
: null}
</View>
</NewAdHoc>
);
}
if you have any suggestion with my solution, please feel free to contact me.
Related
return arr.map((entry, index) =>
<View style={ this.state.interests.includes(entry) ? styles.optionSelected : styles.option}>
<TouchableOpacity onPress={() => this.addToInt(entry)}>
<Text style={{ color: "#22305D", paddingVertical: 15, paddingHorizontal: 25, fontSize: 18 }} key={entry}>{`${entry}`}</Text>
</TouchableOpacity>
</View>
)
I currently have this code to traverse my array and display all the values, but I want a way to change the styling of an individual object when pressed. The way I have it now, onPress adds that unique value to the array interests (100% sure this works), but the conditional styling isn't working. Is there a different way of handling this? I am trying to avoid setting a state for option1, option2, etc.. Thanks!
I have a react-native component which is rendering 2 Icons for marking a favorite and writing a comment as shown below:
function RenderDish(props) {
const dish = props.dish;
if (dish != null) {
return (
<Card featuredTitle={dish.name} image={{ uri: baseUrl + dish.image }}>
<Text style={{ margin: 10 }}>
{dish.description}
</Text>
<View style={{ display: "flex", flexDirection: "row", alignItems: "center", justifyContent: "center" }}>
<Icon raised reverse name={props.favorite ? 'heart' : 'heart-o'} type='font-awesome' color='#f50'
onPress={() => props.favorite ? console.log('Aleady Favorite') : props.onFavorite()} />
<Icon raised reverse name='pencil' type='font-awesome' color='#3b5998'
onPress={() => props.onComment()} />
</View>
</Card>
);
}
else {
return (<View></View>);
}}
I am calling this functional component from the outer component as shown below:
<RenderDish dish={this.props.dishes.dishes[+dishId]}
favorite={this.props.favorites.some(el => el === dishId)}
onFavorite={() => this.markFavorite(dishId)}
onComment={() => this.toggleModal()} />
I have already implemented the toggleModal() and the markFavorite() methods and everything is working as expected but my question is: Is there any other way of passing 2 or more different event handlers through a single prop ? For eg. Is there any way to say something like: <RenderDish dish={xyz} onPress={()=> handler1 AND handler2}. Or is there any elegant alternative to what I have done(if I had 5 buttons I would need 5 props :( ) ?
You can have your handlers in a single function and call the function in onPress.
or just
onPress={()=> {handler1, handler2}}
Try something like below. Pass the handler methods as an object in a props.
function Button(props) {
return (
<div>
<button type="button" onClick={props.onPress.handler1} >Handler1</button>
<button type="button" onClick={props.onPress.handler2} >Handler2</button>
</div>
);
}
class Home extends Component {
handler1() {
console.log('handler 1');
}
handler2() {
console.log('handler 2');
}
render() {
return (
<div>
<Button onPress={{ handler1: this.handler1, handler2: this.handler2 }} />
</div>
)
}
}
The way that you’ve done it is probably the most common way and perfectly acceptable.
It’s not generally a problem if there are only a few handlers (and they aren’t being passed deeply).
You could bundle them into an object and single prop, but I don’t typically see it done that way and I don’t think there’s a strong benefit.
You can also pass in multiple props as a single spread object without having to bundle them into a single prop.
If you start getting a more handlers than you’re comfortable with, then that would probably be a good time to look at how you’re handling your state management. You may be better off with a reducer and dispatching actions at that point.
Update
A few other notes about your code:
You can destructure props for easier use.
You do not need a separate closing tag for View if there is no content.
You do not have to make new arrow functions for event handlers if all you are doing is calling another arrow function. You can just set it to the first arrow function.
Consider a ternary to allow an implicit return.
function RenderDish({dish, favorite, onFavorite, onComment}) =>
dish
? <Card featuredTitle={dish.name} image={{ uri: baseUrl + dish.image }}>
<Text style={{ margin: 10 }}>
{dish.description}
</Text>
<View style={{ display: "flex", flexDirection: "row", alignItems: "center", justifyContent: "center" }}>
<Icon raised reverse name={favorite ? 'heart' : 'heart-o'} type='font-awesome' color='#f50'
onPress={() => favorite ? console.log('Aleady Favorite') : onFavorite()} />
<Icon raised reverse name='pencil' type='font-awesome' color='#3b5998'
onPress={onComment} />
</View>
</Card>
: <View/>
I would focus more on these items, rather than bundling those two functions.
I have a very frustrating situation. Trying to get keyboard to disappear and detect onPress event handler in child row.
Here is what my code looks like:
_renderRow = (prediction) => {
return (
<TouchableOpacity onPress={() => {
Keyboard.dismiss();
this.setState({ location: prediction.description });
}}>
<View style={styles.listItemContainer}>
<Text>{prediction.description}</Text>
</View>
</TouchableOpacity>
)
}
render() {
return (
<View style={styles.wrapper}>
{/* style={[this.state.predictions.length > 0 ? styles.searchContainerSuggest : styles.searchContainer]} */}
<View style={styles.searchContainerSuggest}>
<View style={{paddingLeft: 10, height: 45, display: 'flex', justifyContent: 'center'}}>
<TextInput
placeholder="Enter location"
value={this.state.location}
onChangeText={location => this.onChangeLocation(location)}
style={styles.textInput}
/>
</View>
{this.state.predictions.length && this.state.location !== '' ?
<FlatList
keyboardShouldPersistTaps={'handled'}
refreshing={!this.state.loaded}
initialNumToRender={10}
enableEmptySections={true}
data={this.state.predictions}
keyExtractor={(_, index) => index.toString()}
renderItem={ ({item: prediction}) => this._renderRow(prediction) } />
: null}
</View>
</View>
);
}
I probably need a helping hand or two with regards to how to debug this issue.
Looked up several examples on how to deal with hiding the keyboard and allowing a particular selection to be pressed at the same time.
I thought that keyboardShouldPersistTaps would allow for the child selection to be selected. Upon selection, the onPress event handler will trigger and that will be where I call Keyboard.dismiss() to hide the keyboard. Does not seem to work.
In my case, besides adding keyboardShouldPersistTabs='handled' to the FlatList in question, it was also needed to add keyboardShouldPersistTabs='handled' and nestedScrollEnabled={true} to a parent ScrollView like 2 levels above, wrapping the FlatList I intended to get this behavior with. Check out this issue in react-native repo for more info.
For anyone who is running into the same problem as me. Check whether your FlatList or ScrollView is nested in another FlatList or ScrollView.
If yes, then add
keyboardShouldPersistTaps='handled'
to the element as a props as well.
add keyboardDismissMode="none" to FlatList
Until expo lets me use Realm databases ive decided to go with Json and asyncstorage. The source data originates from Json anyway so for this app it makes sense to leave it as json.
I have a flatlist with the json displayed. To the left of each item in the list is a star icon.
When I touch each item in the list the star will go solid to indicate it has been pressed. Press it again, the icon will be an outline of a star to indicate it has been de-selected.
The onpress function looks like this symbols is the name of the JSON data the JSON data has 3 keys... symbol, name and iconName. Symbol is the item in the flatlist that is touched.
onPressListItem = ( symbol ) => {
for (var i = 0; i < this.state.symbols.length; i++){
if (this.state.symbols[i].symbol === symbol){
const copyOfSymbolsList = [...this.state.symbols];
if (copyOfSymbolsList[i].iconName === 'md-star') {
copyOfSymbolsList[i].iconName = 'md-star-outline';
} else {
copyOfSymbolsList[i].iconName = 'md-star';
}
this.setState({ symbols:copyOfSymbolsList });
}
}
}
So as you can see above it basically just scrolls through the entire json array to find the appropriate row and then makes a copy of the list, changes the data and then sets state again.
The app isn't super fast maybe half a second delay before the icon changes on my pixel 2 and there is only 100 records in the list. Im a little worried if the list gets into the thousands of rows itll be really bad.
Is there a better/faster/simpler/more react-y way to solve this problem?
EDIT
This is the renderListItem function which calls the onPress function
renderListItem = ({ item }) => {
return (
<TouchableOpacity
onPress={() => this.onPressListItem(item.symbol)}
>
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{backgroundColor: 'powderblue'}}>
<Ionicons style={styles.listItemIcon} name={item.iconName} />
</View>
<View style={{backgroundColor: 'skyblue'}}>
<Text style={styles.listItem}>
{item.name.toUpperCase()} {item.symbol}
</Text>
</View>
</View>
</TouchableOpacity>
);
};
EDIT #2
This is the FlatList code.
<View style={styles.mainContainer}>
<FlatList
data={this.state.symbols}
keyExtractor= {(item, index) => item.symbol}
ItemSeparatorComponent={this.renderListSeparator}
renderItem={this.renderListItem}
/>
</View>
i guest your json structure is like this.
the_data = [
{'iconName': 'xxx', 'symbol': '<i class="fa fa-items"></i>'}
]
You can access this structure by index, passing the index to the onPressListItem function.
renderListItem = ({ item }, index) => {
return (
<TouchableOpacity
onPress={() => this.onPressListItem(index)}
>
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{backgroundColor: 'powderblue'}}>
<Ionicons style={styles.listItemIcon} name={item.iconName} />
</View>
<View style={{backgroundColor: 'skyblue'}}>
<Text style={styles.listItem}>
{item.name.toUpperCase()} {item.symbol}
</Text>
</View>
</View>
</TouchableOpacity>
);
};
With this design you don't have to iterate your json.
onPressListItem = ( index ) => {
the_dataobj = the_data[index];
the_dataobj.iconName = 'md-start';
if (the_dataobj.iconName === 'md-start'){
the_dataobj.iconName = 'md-start_outline';
}
//you don't need a extra else here, is in-necesarry
the_data[index] = the_dataobj;
//set state ... do more
}
BTW: This is have nothing to do with react, the correct design of your workflow should be independent of the framework library.
Happy Codding!!!
For those of you reading, the accepted answer did not update the rendered list properly on screen. So the actual working code for me is below. Im pretty sure its because the accepted answer did not use the this.setState function to write the values back to the array.
Also the if then else needs to have an else in it because when the user taps the same row twice i want it to reverse its changes.
All that being said, the updates are still very slow. About the same speed as it was when using the for loop in the question. Not sure why.
onPressListItem = ( index ) => {
const copyOfSymbolsList = [...this.state.symbols];
thePressedSymbol = copyOfSymbolsList[index];
if (thePressedSymbol.iconName === 'md-star') {
thePressedSymbol.iconName = 'md-star-outline';
}else{
thePressedSymbol.iconName = 'md-star';
}
copyOfSymbolsList[index] = thePressedSymbol;
this.setState({ symbols:copyOfSymbolsList });
}
I am trying to make the price of product change, when user select different weight.
For example lets say user want to buy mango, default i am showing price of mango /kg, now if user change weight from 1kg to 500gm then the value should change.
I have on showing the product and drop down option to show available weights. But not sure how to change the value of the of price based on weights.
Here is my code to display list of products and weight options
PS: This is a product listing screen where i want to make the change to happen
Productlisting.js
render() {
return (
<View style={styles.productListingContainer}>
<ListView
enableEmptySections={ true }
dataSource={ this.state.dataSource }
renderRow={ this._renderRow } />
</View>
)
}
//Rander products
_renderRow =(rowData)=> {
return (
<ProductThumb { ...rowData } onPress={() => this._pressProduct(rowData.id) } />
)
};
Productcard.js
//Onvalue change
onValueChange(value: string) {
this.setState({
selected: value,
});
}
//This is render content
<TouchableOpacity style={ styles.row } onPress={ this.props.onPress }>
<Image style={ styles.image } source={{ uri: rowData.imageUri }} />
<View style={ styles.textsHolder }>
<Text ellipsizeMode='tail' numberOfLines={2} style={ [styles.name,stylesheet.mb4] }>{ rowData.name } </Text>
<View style={[{flexDirection: 'row'},stylesheet.mb4]}>
<Text style={ styles.prize }>$ { rowData.prize } </Text>
{
(rowData.regularPrize) ? <Text style={ styles.regularPrize }>$ { rowData.regularPrize }</Text>: null
}
</View>
<View style={{flexDirection: 'row',flex:1}}>
<View style={styles.pickerWraper}>
<Picker
mode="dropdown"
style={styles.picker}
selectedValue="200gm"
itemStyle={styles.pickerItem}
onValueChange={this.onValueChange.bind(this)}
>
<Item label="200gm" value="key0" />
<Item label="1kg" value="key1" />
</Picker>
</View>
<Button iconLeft small style={[stylesheet.pr5,stylesheet.ml5,styles.subbutton]}>
<Icon style={{marginRight:4, marginLeft:6,color:colors.white}} active name="cart" />
<Text style={{color:colors.txt_white}}>Subscribe</Text>
</Button>
</View>
</View>
</TouchableOpacity>
Being a beginner to react native, and javascript framework as whole. i am really not sure what to look for to begin with. If anyone can give some pointer that will be very helpful. Thank you
I would solve this by basically following these steps:
In your render() method, you can basically set up your own variable to show the user a dynamic price. For instance, const displayPrice = !this.state.selected ? this.props.item.price : getPriceWithUnit(this.state.selected, this.props.item.price). Here I've checked to see if a unit has been selected (assuming the default is null). If not, then I use the default item price. If there is a saved unit that the user selected, then I use the value generated by the function below:
Set up a function getPriceWithUnit(unit, price) which basically converts the original price into whatever the new display price needs to be depending on the unit, where the unit is whatever you need via the selected state reference.
Instead of using the item price where you display it in the component, use this new variable displayPrice like <Text>{displayPrice}</Text>.
Then, whenever the user changes the value of the selected unit, it will trigger a state update. This will then cause the component to re-render, updating this variable as it does so.