Dynamically change function onPress handler in react-native - javascript

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>)} />
}

Related

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

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>

How to change change and track boolean state of each element in an array

Im trying to make it so that when a TouchableHighlight is pressed in react native it goes from one colour to another. I have state that is set to false and changes when the button is pressed. However this changes the colour for all elements in the map rather than for each item individually. Is there a way to update the state for each element independently?
Here is my code:
function OnboardingVibes() {
const [pressed, setPressed] = useState(false);
return (
<View style={{marginTop: 40}}>
<Text style={{fontSize: 22, color: '#FFF', marginBottom: 16}}>Vibes</Text>
<View style={styles.buttonContainer}>
{vibes.map((vibe) => {
return (
<TouchableHighlight onPress={() => setPressed(true)} style={{backgroundColor: pressed ? '#DDD' : '#4D2C5B', margin: 4, borderRadius: 4}} key={`vibe-${vibe}`}>
<Text style={styles.vibeButton}>{vibe}</Text>
</TouchableHighlight>
)
})}
</View>
</View>
);
}
One way to do it is to move the state down so you could have individual states. When the state is true at the top-level, all child components will receive the same state.
function TouchableVibe({vibe}) {
const [pressed, setPressed] = useState(false);
return (
<TouchableHighlight
onPress={() => setPressed(true)}
style={{
backgroundColor: pressed ? "#DDD" : "#4D2C5B",
margin: 4,
borderRadius: 4,
}}
>
<Text style={styles.vibeButton}>{vibe}</Text>
</TouchableHighlight>
);
}
function OnboardingVibes() {
return (
<View style={{ marginTop: 40 }}>
<Text style={{ fontSize: 22, color: "#FFF", marginBottom: 16 }}>
Vibes
</Text>
<View style={styles.buttonContainer}>
{vibes.map((vibe) => (
<TouchableVibe key={`vibe-${vibe}`} vibe={vibe} />
))}
</View>
</View>
);
}
const [pressed, setPressed] = useState(vibes.map(e=>false));
{vibes.map((vibe, index) => {
return (
<TouchableHighlight onPress={() => {
let new_Pressed =vibes.map(e=>false)
new_pressed[index]=true;
setPressed(new_pressed);
}}
style={{backgroundColor: pressed[index]
? '#DDD'
: '#4D2C5B',
margin: 4, borderRadius: 4}} key={`vibe-${vibe}`}>
<Text style={styles.vibeButton}>{vibe}</Text>
</TouchableHighlight>
)
})}
Instead of passing in a boolean to the state, pass in the id/vibe instead, and then in your conditional logic within the map you can determine if that single item is the same as the one you've clicked.
function OnboardingVibes() {
const [pressed, setPressed] = useState('');
return (
<View style={{marginTop: 40}}>
<Text style={{fontSize: 22, color: '#FFF', marginBottom: 16}}>Vibes</Text>
<View style={styles.buttonContainer}>
{vibes.map((vibe) => {
return (
<TouchableHighlight onPress={() => setPressed(vibe)} style={{backgroundColor: pressed === vibe ? '#DDD' : '#4D2C5B', margin: 4, borderRadius: 4}} key={`vibe-${vibe}`}>
<Text style={styles.vibeButton}>{vibe}</Text>
</TouchableHighlight>
)
})}
</View>
</View>
);
}

React Native TextInput Value persist when Tab is changed

I have encountered a weird issue in the newest react native where the value in the text input in a component remains when a tab is switched.
I can't figure what is going on and I thought it should re-render when tab is changed but it doesn't.
Here's my code
app.js
export default function App() {
const [tab, setTab] = useState("TAB1")
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row' }}>
<TouchableOpacity
style={{ borderRadius: 5, borderWidth: 1, marginRight: 5, padding: 20 }}
onPress={() => setTab("TAB1")}
>
<Text>Tab 1</Text>
</TouchableOpacity>
<TouchableOpacity
style={{ borderRadius: 5, borderWidth: 1, padding: 20}}
onPress={() => setTab("TAB2")}
>
<Text>Tab 2</Text>
</TouchableOpacity>
</View>
<View style={{ marginTop: 20}}>
{
tab === "TAB1" ? (
<View>
<InputComponent tab={tab} />
</View>
) : (
<View>
<InputComponent tab={tab} />
</View>
)
}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
marginTop: 100,
padding: 10
},
});
inputcomponent.js
export function InputComponent({ tab }) {
const [value, setValue] = useState("");
return (
<View>
<Text>{tab}</Text>
<TextInput placeholder="INPUT HERE" value={value} onChangeText={setValue}/>
</View>
)
}
It seems like the input component re-renders but the text input within it doesn't change.
Demo Issue
This is such a good question. This is because we are importing it once and passing it to two different components. It changes the tab but uses the same textinput state because they are using the same key.
To fix this pass in the key prop so React knows that tab changed:
{
tab === "TAB1" ? (
<View>
<InputComponent key={1} tab={tab} />
</View>
) : (
<View>
<InputComponent key={2} tab={tab} />
</View>
)
}
Snack: https://snack.expo.io/mVVLb9uId
Read about keys: https://reactjs.org/docs/lists-and-keys.html#keys

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.

Not able to access the absolute position view in react-native android

I have a component called MDropDowlList which render the following view :
MDropDownList.js
render() {
return (
<View>
<View
style={{
backgroundColor: '#fff',
position: 'absolute',
left: 0,
right: 0,
top: 0,
maxHeight: 200,
}}
>
{
this.props.searchable && (
<TextInput
style={{ zIndex: 198 }}
onChangeText={(text) => this.search(text)}
/>
)
}
<FlatList
data={this.state.data}
keyboardShouldPersistTaps="always"
nestedScrollEnabled={true}
contentContainerStyle={{ zIndex: 199 }}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => (
<TouchableHighlight
key={index.toString()}
style={{
padding: 10,
backgroundColor: '#fff',
}}
underlayColor="#5897fb"
onPress={() => this.props.handleOnPress(item)}
>
<MText size={18}>{item}</MText>
</TouchableHighlight>
)}
/>
</View>
</View>
);
}
Then I have called this component to another component called MDropDown which render method is as below :
MDropDown.js
render() {
return (
<View style={{ marginVertical: 5, zIndex: 3}}>
...
{/* Another components */}
...
{
this.state.displayDropDown && (
<MDropDownList data={this.props.data} searchable={this.props.searchable} handleOnPress={(item) => this.handleOnPress(item)} />
)
}
</View>
);
}
Now finally I called my MDropDown component in my main screen as follow :
render() {
return (
<KeyboardAvoidingView style={{ flex: 1, backgroundColor: Colors.bgGray }} behavior="padding">
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={styles.container} keyboardShouldPersistTaps="always" nestedScrollEnabled={true}>
<View style={{ backgroundColor: '#fff', padding: 10 }}>
<MDropDown
label="Category"
placeholder="Select Category"
data={["test1", "test2", "test3", "test4", "test5"]}
onSelectItem={(selectedItem) => alert(selectedItem)}
searchable={true}
/>
</View>
</ScrollView>
</KeyboardAvoidingView>
)
}
But I am not able to access Flatlist item or TextInput of MDropDownList component. Even I am not able to focus TextInput available in MDropDownList.
Please help me what's going wrong here ???
Note : This issue is only on android, on ios it is working properly. On ios I am able to click flatlist item and focus TextInput as well as.
Tried using zIndex to the absolute view?
Add property of zIndex : 500 to the style.

Categories

Resources