In react-native, how to add active class to selected menu option - javascript

I have the following menu options in the following structure:
const menuOptions = [
{
route: 'RegisterStack',
label: 'Register',
size: 25,
},
{
route: 'LoginStack',
label: 'Login',
size: 25,
},
{
route: 'DashboardStack',
label: 'Dashboard',
size: 25,
}
];
and here's my remaining code with component state and the rest of the code:
const [activeLink, setActiveLink] = useState('');
const navigation = useNavigation();
<View>
{menuOptions.map((stack, index) => {
return (
<TouchableOpacity
key={index}
style={[
styles.menuStyle,
index === activeLink
? {backgroundColor: 'red'}
: {backgroundColor: 'white'}
]}
onPress={() => navigation.navigate(stack.route)}>
<Icon
name={stack.icon}
color={stack.color}
size={stack.size}
style={{
marginRight: mainMenuOpen ? '10%' : 0,
}}
onPress={() => setActiveLink(index)}
/>
<Text style={{width: '70%', fontWeight: '500'}}>
{stack.label}
</Text>
</TouchableOpacity>
);
})}
</View>
With the current code, I can see active class taking place but it seems to broke the navigation and won't route to a different page when click on it. The navigation starts working fine when I remove the following onPress method from Icon onPress={() => setActiveLink(index)}.
Can someone please help me how can I make this work?

The problem here is that you are nesting two pressables. The touch event will be caught by onPress of the Icon component. This is also the reason why the navigation starts working when you remove the inner onPress function.
It seems to me that you either want setActiveLink to be triggered if we press on the Icon only or you want setActiveLink to be triggered and then navigate to the desired screen at the same time.
In the second case you just need one onPress function for the outer component.
<TouchableOpacity
key={index}
style={[
styles.menuStyle,
index === activeLink
? {backgroundColor: 'red'}
: {backgroundColor: 'white'}
]}
onPress={() => {
setActiveLink(index)
navigation.navigate(stack.route)
}}>
<Icon
name={stack.icon}
color={stack.color}
size={stack.size}
style={{
marginRight: mainMenuOpen ? '10%' : 0,
}}
/>
<Text style={{width: '70%', fontWeight: '500'}}>
{stack.label}
</Text>
</TouchableOpacity>
If you want two separate things to happen, e.g. setActiveLink on Icon press only and navigate on stack.label press only, then you need to reorganize your component. You could create a parent view which layouts the components in a row without nesting two pressables.
// you still need to apply the desired styles...
<View>
<Pressable onPress={() => setActiveLink(index)}>
<Icon
name={stack.icon}
color={stack.color}
size={stack.size}
/>
</Pressable>
<TouchableOpacity
key={index}
onPress={() => {
navigation.navigate(stack.route)
}}>
<Text style={{width: '70%', fontWeight: '500'}}>
{stack.label}
</Text>
</TouchableOpacity>
</View>

Related

React Native Touchable Opacity

I have this code, in which TouchableOpacity is not working and i don't know why:
const Box = (props) => {
return (
<View>
<TouchableOpacity onPress={() => {props.callBackProp(); props.budCallBackProp(props.name)}}>
<View style={styles.inputBox}>
<Text style={styles.testText}>{props.name}</Text>
</View>
</TouchableOpacity>
</View>
);
}
What is wrong here?
Edit:
At first, i click on this blue button with "budynek" text inside it, which opens something that looks like a list. Then after i click on some option i want it to dissapear, which is what these callbacks are for. I wrapped each option in this TouchableOpacity component but nothing happens after i click it - there is no effect of a click. A view which holds these "Box" component is absolutely positioned. Here is rest of the code:
return (
<View style={styles.rozwijanaPoz}>
<Pusty/>
<Box name={"budynek 1"} callBackProp={props.callBack} budCallBackProp={props.budCallBack}/>
<Box name={"budynek 2"} callBackProp={props.callBack} budCallBackProp={props.budCallBack}/>
<Box name={"budynek 3"} callBackProp={props.callBack} budCallBackProp={props.budCallBack}/>
</View>
);
}
const styles = StyleSheet.create({
rozwijanaPoz: {
position: "absolute",
left: 60,
},
});
And the main container:
<TouchableOpacity onPress={() => setIsVisible(true)}>
<View style={styles.budynekContainer}>
<Text style={styles.budynekTekst}>{budynekText}</Text>
{isVisible ?
<Rozwijana callBack={visibleCallBack} budCallBack={budynekCallBack}/>
: null}
</View>
</TouchableOpacity>

React-native-paper Menu is hiding behind other elements despite use of zIndex. How do i bring the element on top?

I am using the Menu component from react-native-paper for options menu on modal-header.
Below is the screenshot of the modal:
The parent tag holding the Menu has sibling elements (stuff below the header).
It seems that due to this heirchy, the menu is being rendered under other elements.
I tried override this overlaping of elements by assigning possition:"absolute", zIndex: 100.
zIndex is haveing no effect on the way it is being overlaped. I tried varying the zIndex from 1 to 1500, but its had n effect either.
Following is the code for Menu Component wrapper (ModalOptions):
const ModalOptions = () => {
const [visible, setVisible] = React.useState(false);
const openMenu = () => setVisible(true);
const closeMenu = () => setVisible(false);
return (
<Provider>
<View>
<Menu
style={{ backgroundColor: "#222", borderWidth: 2, top:150, left:-100 , position: 'absolute', zIndex:100 }}
visible={visible}
onDismiss={closeMenu}
anchor={
<TouchableOpacity onPress={openMenu}>
<ThreeDotIcon size={35} color={colors.darkGrey} />
</TouchableOpacity>
}>
...
</Menu>
</View>
</Provider>
);
};
I guess I'm not using zIndex properly...
If so, how should I be using it instead?
If not, is there any other way to get this done?
or maybe I need to re format the code in such way that the heirchal level of Menu is increased
but I would really not prefer going this way.
You can use a View to wrap outsize the provider with style={{zIndex: 100}}, like below
<View style={{zIndex: 100}}>
<Provider>
<View>
<Menu
style={{ backgroundColor: "#222", borderWidth: 2, top:150, left:-100 , position: 'absolute', zIndex:100 }}
visible={visible}
onDismiss={closeMenu}
anchor={
<TouchableOpacity onPress={openMenu}>
<ThreeDotIcon size={35} color={colors.darkGrey} />
</TouchableOpacity>
}>
...
</Menu>
</View>
</Provider>
</View>

Automatically scroll ScrollView in KeyboardAvoidingView when I add a new TextInput

I am working with a KeyboardAvoidingView and it's working perfectly fine except for one small issue. In the code below I have a TouchableOpacity that when clicked runs the function addName() which appends to an array and creates a new TextInput - basically when you click it, it adds a new TextInput to the ScrollView.
The KeyboardAvoidingView works perfectly fine except every time a new TextInput is added/rendered, I have to scroll down to see it. Do you know how I can make it automatically scroll to the bottom when a new TextInput is rendered?
Here is my code for the KeyboardAvoidingView:
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS == "ios" ? "padding" : "height"}
>
<HeaderComponent
name={this.props.route.params.bill.barName + " Tab"}
navigation={this.props.navigation}
goHome={true}
goHomePrompt={true}
/>
<View
style={{
marginTop: 30,
marginLeft: 10,
}}
>
<Text
style={{ color: "white", fontSize: 18 }}
allowFontScaling={false}
>
Add people to split with...
</Text>
</View>
<ScrollView keyboardShouldPersistTaps={"handled"}>
{this.state.nameInput.map((value, index) => (
<View style={styles.nameContainer} key={index}>
<View
style={{
width: "90%",
}}
>
<TextInput
style={styles.textInputContainer}
value={value}
onChange={this.handleText(index)}
placeholder={"Enter name..."}
placeholderTextColor={"#333333"}
maxLength={50}
/>
</View>
<View
style={{
width: "10%",
}}
>
<TouchableOpacity
onPress={() => this.handleDelete(index)}
>
<Icon name="cancel" type="material" color="red" />
</TouchableOpacity>
</View>
</View>
))}
<TouchableOpacity onPress={() => this.addName()}>
<Text style={styles.addPerson}>+ Add Person</Text>
</TouchableOpacity>
</ScrollView>
<View style={styles.bottomContainer}>
<TouchableOpacity
style={styles.continueButton}
onPress={() => {
// filters through array to make sure there are no empty strings
let nameInput = this.state.nameInput.filter(
(name) => name !== ""
);
if (
nameInput.length > 1 &&
new Set(nameInput).size === nameInput.length
) {
this.setState({ nameInput, loading: false });
} else {
alert(
"Please make sure there are at least two people and no duplicate names!"
);
}
}}
>
<Text style={styles.continueButtonText} allowFontScaling={false}>
Continue to Split Tab
</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
And here is my code for the addName() function:
addName = () => {
let nameInput = this.state.nameInput.concat("");
this.setState({
nameInput,
});
};
This page has the solution I was looking for: Is it possible to keep a ScrollView scrolled to the bottom?

Different content on React Native based on condition

on my react-native app I would like to have premium content for people who paid a subscription.
My issue is how I make the content to display as unavailable(if you are not premium ) and the same as the other content if you are premium. Basically I would like the premium content to be displayed with a "lock overlay" on it for non-premium users.
However, I do not know how I set this conditional. It is a matter of state? If yes where should be positioned this state considering that is unidirectional?
Just to be clear I will have premium and non premium content
class Browser extends Component {
scrollX = new Animated.Value(0);
renderRecommended = () => {
return (
<View style={[styles.flex, styles.column, styles.recommended]}>
<View style={[styles.row, styles.recommendedHeader]}>
<Text
style={{
fontSize: theme.sizes.font * 1.4,
alignSelf: 'center',
color: 'white',
fontFamily: 'Nunito-Bold',
}}>
Recommended
</Text>
</View>
<View style={[styles.column, styles.recommendedList]}>
<FlatList
horizontal
scrollEnabled
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
snapToAlignment="center"
style={[styles.shadow, {overflow: 'visible'}]}
data={this.props.destinations}
keyExtractor={(item, index) => `${item.id}`}
renderItem={({item, index}) =>
this.renderRecommendation(item, index)
}
/>
</View>
</View>
);
};
renderRecommendation = (item, index) => {
const {destinations} = this.props;
const isLastItem = index === destinations.length - 1;
const {navigation} = this.props;
return (
<TouchableOpacity
onPress={() =>
this.props.navigation.navigate('PreScreen', {
item,
})
}>
<View
style={[
styles.flex,
styles.column,
styles.recommendation,
styles.shadow,
index === 0 ? {marginLeft: theme.sizes.margin} : null,
isLastItem ? {marginRight: theme.sizes.margin / 2} : null,
]}>
<ImageBackground
style={[styles.imageback]}
source={{uri: item.preview}}
/>
</View>
<View
style={[
index === 0 ? {marginLeft: theme.sizes.margin - 10} : null,
isLastItem ? {marginRight: theme.sizes.margin / 2} : null,
]}>
<Text
style={{
fontSize: theme.sizes.font * 1.25,
fontWeight: '200',
color: 'white',
marginLeft: 10,
//paddingBottom: 20,
fontFamily: 'Nunito-Bold',
}}>
{item.title}
</Text>
<Text
style={{
color: theme.colors.caption,
marginLeft: 10,
fontFamily: 'Nunito-SemiBold',
}}>
{item.location}
</Text>
</View>
</TouchableOpacity>
);
};
render() {
return (
<ScrollView style={styles.container}>
<BackgroundSvg style={styles.background} />
<ScrollView
style={styles.contentContainer}
showsVerticalScrollIndicator={false}
contentContainerStyle={{paddingBottom: theme.sizes.paddin}}>
{this.renderRecommended()}
{this.renderRecommended2()}
{/* <View style={styles.mainContainerView}>
<TouchableOpacity style={styles.singInButton} gradient>
<Text style={styles.logInText}>
Activate premium subscription
</Text>
</TouchableOpacity>
</View> */}
</ScrollView>
</ScrollView>
);
}
}
Browser.defaultProps = {
destinations: mocks,
reading: readingList,
};
export default Browser;
My code is the one on the top. Just to simplify I am accesing some elements from JSON and I am creating Flatlist based on this. What I want is is to give some of the JSON files a bolean with premium or not and in this way to make some elements available for user or not.
As part of your destinations array, for each object, you can specify a boolean field called isPremiumItem. Your users should have a boolean field like isPremiumUser to show the type of their subscription.
Then in renderRecommended method you can check the user subscription status (isPremiumUser), and also the specific item status (isPremiumItem). Then you can render accordingly.

Dynamically change function onPress handler in react-native

Is there a way to dynamically change the onPress function handler in a <FlatList>? I have three onPress functions which I need passed via an array. How can I update the function or function name that is passed to <TouchableOpacity> dynamically (see my code below)?
My input array:
const menu = [
{
"id": 1,
"route": "this.onBottonPressedHome.bind(this)",
"info": "1"
},
{
"id": 2,
"route": "this.onBottonPressedSettings.bind(this)",
"info":"2"
},
{
"id": 3,
"route": this.onBottonPressedHistory.bind(this)",
"info": "3"
}
]
My FlatList:
<FlatList
horizontal={true}
data={menu}
keyExtractor={item => item.id.toString()}
showsHorizontalScrollIndicator={false}
renderItem={({ item, index }) => (
<TouchableOpacity
onPress={item.route}
>
<Card>
<CardItem style={{ backgroundColor: '#37BBE1'}}>
<Body>
<View style={{ paddingTop: 10, width: 135 }}>
<Text style={{ fontSize: 12, textAlign:'justify', color: '#fff' }}>
{item.info}
</Text>
</View>
</Body>
</CardItem>
</Card>
</TouchableOpacity>
When I do this, I get a error stating that bind is not defined in the array. How can I solve this ?
Update:
I have achieved this using ternary operators, is it good ? Will this create any problems in performance issues ?
If I understand correctly, your menu array consists of object items that have string value fields (ie no "real" function bindings).
Assuming this is a constraint that you have to work around, you might consider a different approach to this problem, where instead of dynamically passing the onPress handler at render time, you instead dynamically resolve the handler during the event like so:
render() {
/* This handler dynamically selects the action to call, based on the "item"
that the user has pressed */
const onPressItemHandler = (item) => {
switch(item.id) {
case 1: {
this.onBottonPressedHome();
break;
}
case 2: {
this.onBottonPressedSettings();
break;
}
case 3: {
this.onBottonPressedHistory();
break;
}
}
}
return <FlatList
horizontal={true}
data={menu}
keyExtractor={item => item.id.toString()}
showsHorizontalScrollIndicator={false}
renderItem={({ item, index }) => (
<TouchableOpacity
onPress={ () => onPressItemHandler(item) }>
<Card>
<CardItem style={{ backgroundColor: '#37BBE1'}}>
<Body>
<View style={{ paddingTop: 10, width: 135 }}>
<Text style={{
fontSize: 12,
textAlign:'justify',
color: '#fff' }}>
{item.info}
</Text>
</View>
</Body>
</CardItem>
</Card>
</TouchableOpacity>)} />
}

Categories

Resources