Different content on React Native based on condition - javascript

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.

Related

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

Page slider not working On Android, works fine in iOS

The code below shows how all the functions are implemented
On iOS everything works fine, the button changes slide, the dots on the bottom change state based on the page and on the last page it displays a signup button,
On Android, the button works only on the first page, the last page doesnt show the signup button, and next button doesnt work on the second page!
export default function introScreen({ navigation }) {
const [sliderState, setSliderState] = useState({ currentPage: 0 });
const scrollRef = useRef();
const { width, height } = Dimensions.get('window')
const notchSize = StatusBar.currentHeight
const setSliderPage = (event: any) => {
const { currentPage } = sliderState;
const { x } = event.nativeEvent.contentOffset;
const indexOfNextScreen = Math.floor(x / width);
if (indexOfNextScreen !== currentPage) {
setSliderState({
...sliderState,
currentPage: indexOfNextScreen,
});
}
};
const { currentPage: pageIndex } = sliderState;
const onPressTouch = () => {
scrollRef.current?.scrollTo({
x: width*(pageIndex+1),
animated: true,
});
}
return (
<>
<StatusBar hidden />
<SafeAreaView style={{ flex: 1, backgroundColor:'black' }}>
<ScrollView
style={{ flex: 1 }}
horizontal={true}
scrollEventThrottle={16}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
onScroll={(event: any) => {
setSliderPage(event);
}}
ref={scrollRef}
>
<View style={{ width, height, alignItems: 'center', }}>
<Image source={require('../../assets/images/Intorduction/1.png')} style={styles.imageStyle} />
<BlurView tint={'dark'} intensity={70} style={{width:'80%',borderRadius:10, marginTop:10}}>
<Text style={[styles.textStyle,{color:'rgb(255, 190, 46)'}]}bla bla</Text>
</BlurView>
</View>
<View style={{ width, height, alignItems: 'center' }}>
<Image source={require('../../assets/images/Intorduction/2.png')} style={styles.imageStyle} />
<BlurView tint={'dark'} intensity={70} style={{width:'80%',borderRadius:10, marginTop:10}}>
<Text style={[styles.textStyle,{color:'rgb(255, 190, 46)'}]}>bla bla</Text>
</BlurView>
</View>
<View style={{ width, height, alignItems: 'center' }}>
<Image source={require('../../assets/images/Intorduction/3.png')} style={styles.imageStyle} />
<BlurView tint={'light'} intensity={70} style={{width:'80%',borderRadius:10, marginTop:10}}>
<Text style={[styles.textStyle,{color:'rgb(241, 250, 238)'}]}>bla bla</Text>
</BlurView>
</View>
</ScrollView>
{pageIndex != 2 ?
<View style={styles.paginationWrapper}>
{Array.from(Array(3).keys()).map((key, index) => (
<View style={[styles.paginationDots, { opacity: pageIndex === index ? 1 : 0.2 }]} key={index} />
))}
</View>
:
<></>
}
{pageIndex != 2 ?
<BlurView tint={'dark'} intensity={70} style={styles.nextButton}>
<TouchableOpacity style={{flex:1, justifyContent:'center', alignItems:'center'}} onPress={onPressTouch} >
<Text style={{color:'rgb(241, 250, 238)',fontFamily: 'poiret-one', fontSize:25}}>
Next
</Text>
</TouchableOpacity>
</BlurView>
:
<BlurView tint={'light'} intensity={70} style={styles.signUpButton}>
<TouchableOpacity style={{flex:1, justifyContent:'center', alignItems:'center'}} onPress={() =>navigation.navigate('Login')} >
<Text style={{color:'black',fontFamily: 'poiret-one', fontSize:25}}>
Sign Up
</Text>
</TouchableOpacity>
</BlurView>
}
</SafeAreaView>
</>
);
}
I have narrowed down the problem to the width and the 'event.nativeEvent.contentOffset', the returning number is close to 1 but it is (0.9), therefor math.floor was giving 0 as the index number,
I have changed Math.floor to Math.round and it is working perfectly fine,
If anyone wants a Introduction Slider for their app they can use this

Animate the dynamic height of a view on scroll

I have a UI similar to the image below:
The Title section (the section in dark blue) is supposed to be fixed and the Body section is scrollable.
The height of the Title section would be dynamic as there can be 4 - 5 bullet points retrieved from the server.
My task is to start reducing the height of the Title section when the user starts scrolling upwards to read the content that's present beneath until the point where the user can only read the title in the header. Also, when the user starts scrolling downwards to the point where he/she can see the heading Body, I am supposed to start increasing the height so that he can again start seeing all the bullet points.
This feature is more of like hiding/showing the header on the scroll. But, the title will always be visible.
I have written the below-written code to achieve the same:
import React from 'react';
import {
View,
Text,
ScrollView,
TouchableOpacity,
Linking,
Image,
Animated,
} from 'react-native';
class App extends React.PureComponent<Props, States> {
constructor() {
super();
this.state = {
headerHeight: 0,
headerTitleHeight: 0,
};
this.scrollY = new Animated.Value(0);
}
render() {
const { navigation, pending, macroFeed } = this.props;
const { headerHeight, headerTitleHeight } = this.state;
const { themeDetails } = navigation.state.params;
const {
title, bullet1, bullet2, bullet3,
} = themeDetails;
let headerStyle = {};
if (headerHeight) {
headerStyle = {
height: this.scrollY.interpolate({
inputRange: [0, headerHeight - headerTitleHeight],
outputRange: [headerHeight, headerTitleHeight],
extrapolate: 'clamp',
}),
};
}
const sortedMacroFeed = _.sortBy(macroFeed, (o) => moment(o.date).format('YYYYMMDD')).reverse().slice(0, 5);
if (pending) {
return (
<View style={styles.maxFlex}>
<LoadingSpinner
size="large"
containerStyle={styles.loadingSpinner}
/>
</View>
);
}
return (
<View
style={styles.maxFlex}
>
<Animated.View
style={[styles.headerWrapper, headerStyle]}
onLayout={(event) => {
this.setState({
headerHeight: event.nativeEvent.layout.height,
});
}}
>
<View style={styles.macroBgWrapper}>
<Image source={themeDetails.imgUrl} style={styles.macroBg} />
<View style={styles.macroBgOverlay} />
</View>
<View
style={styles.header}
onLayout={(event) => {
this.setState({
headerTitleHeight: event.nativeEvent.layout.height,
});
}}
>
<TouchableOpacity onPress={() => navigation.goBack()}>
<View>
<Icon name="ios-arrow-back" size={32} style={styles.backIcon} />
</View>
</TouchableOpacity>
<View style={styles.titleWrap}>
<Text style={styles.headerTitle}>
{title}
</Text>
</View>
</View>
<View style={styles.bulletWrapper}>
{
!!bullet1 && (
<View style={styles.column}>
<View style={styles.row}>
<View style={styles.bullet}>
<Text style={styles.buttetListText}>
{'\u2022'}
{' '}
</Text>
</View>
<View style={styles.bulletText}>
<Text style={styles.buttetListText}>
{bullet1}
</Text>
</View>
</View>
</View>
)
}
{
!!bullet2 && (
<View style={styles.column}>
<View style={styles.row}>
<View style={styles.bullet}>
<Text style={styles.buttetListText}>
{'\u2022'}
{' '}
</Text>
</View>
<View style={styles.bulletText}>
<Text style={styles.buttetListText}>
{bullet2}
</Text>
</View>
</View>
</View>
)
}
{
!!bullet3 && (
<View style={styles.column}>
<View style={styles.row}>
<View style={styles.bullet}>
<Text style={styles.buttetListText}>
{'\u2022'}
{' '}
</Text>
</View>
<View style={styles.bulletText}>
<Text style={styles.buttetListText}>
{bullet3}
</Text>
</View>
</View>
</View>
)
}
{
!bullet1 && !bullet2 && !bullet3 && (
<View style={styles.noBulletWrapper}>
<Text style={styles.noBulletPoints}>
No description found.
</Text>
</View>
)
}
</View>
</Animated.View>
<ScrollView
style={styles.maxFlex}
showsVerticalScrollIndicator={false}
onScroll={Animated.event([
{ nativeEvent: { contentOffset: { y: this.scrollY } } },
])}
scrollEventThrottle={16}
>
<View style={[styles.section, styles.wrapGutter]}>
<Text style={styles.sectionTitle}>
Recent Headlines
</Text>
{
sortedMacroFeed.map((feed) => (
<View key={feed.id} style={styles.newsSection}>
<Text style={styles.newsHours}>
{moment(feed.date).fromNow()} | {feed.author}
</Text>
<Text style={styles.newsTitle}>
{feed.title}
<Text onPress={() => this.openWebView(feed.url)}>
<EvilIcons name="external-link" size={16} style={styles.externalLinkIcon} />
</Text>
</Text>
</View>
))
}
</View>
<View style={styles.section}>
<View style={[styles.wrapGutter, styles.sectionTitleWrap]}>
<Text style={styles.sectionTitle}>
Exposure
</Text>
<View style={styles.totalNavWrap}>
<Text style={styles.totalNav}>
$467M
</Text>
<Text style={styles.totalNavLabel}>
Total NaV (all portfolios)
</Text>
</View>
</View>
<Tabs
tabsData={TABS_DATA}
renderTabContent={this.renderTabContent}
tabName="title"
hideIfOneTab
/>
</View>
</ScrollView>
</View>
);
}
}
If you have observed I am trying to retrieve the dynamic height of the title section using onLayout.
This code only works one way i.e when I scroll up. The height of the title section reduces to the point where only the title can be seen and the bullet points get hidden. But after that, I cannot scroll down. The height gets permanently reduced.
Now, if I change the below-given code:
let headerStyle = {};
if (headerHeight) {
headerStyle = {
height: this.scrollY.interpolate({
inputRange: [0, headerHeight - headerTitleHeight],
outputRange: [headerHeight, headerTitleHeight],
extrapolate: 'clamp',
}),
};
}
to
const headerStyle = {
height: this.scrollY.interpolate({
inputRange: [0, 240],
outputRange: [300, 60],
extrapolate: 'clamp',
}),
};
everything seems to work fine. Basically, if I stop retrieving the value of the height dynamically and provide a static value like 240 or something, everything seems to work fine.
But, animation on scroll stops if I accept dynamic height. Any help to solve this would be much appreciated. Thanks in anticipation.
I found the issue. So, if you observe the line:
<Animated.View
style={[styles.headerWrapper, headerStyle]}
onLayout={(event) => {
this.setState({
headerHeight: event.nativeEvent.layout.height,
});
}}
>
onLayout used to setState as soon as the headerHeight value changed on the scroll. Due to which the height of the header used to reduce and scrolling the content down was not possible.
I changed the code to:
<Animated.View
style={[styles.headerWrapper, headerStyle]}
onLayout={(event) => {
if (!this.state.headerHeight) {
this.setState({
headerHeight: event.nativeEvent.layout.height,
});
}
}}
>
Now, everything works fine.

React native specific style item in map

I have this code that generates buttons for size as come from API like (al, l, m, s ... ) and I need to put a specific style when user click in one circle, I have tried this code but it makes the same style for all circles and I need to change the style for one circle when clicked on it :
<View style={{ paddingTop: 10, width: "49%" }}>
<View style={styles.sizeView}>
<View style={styles.sizeView3}>
<Text style={styles.chartText}>{t("size")}</Text>
</View>
</View>
<View style={styles.sizeButtons}>
{this.state.productsList[0].sizes.map((item, index) => {
return (
<TouchableOpacity
key={index}
onPress={() => this.toggle1(item)}
style={
!this.state.pressStatus
? styles.sizes
: styles.sizesAlt
}
onHideUnderlay={this._onHideUnderlay.bind(this)}
onShowUnderlay={this._onShowUnderlay.bind(this)}
>
<Text
key={index}
style={
!this.state.toggle1 ? {} : { color: "#EC1C24" }
}
>
{item}
</Text>
</TouchableOpacity>
);
})}
</View>
</View>
I drag pic for the result
The issue is that you are using one variable to change the toggle status for all the buttons (this.state.toggle1), so if one is toggle they will all be toggled, as i don't think this is the intended behavior.
I suggest to create a pure component for the buttons so that each will have it's own state and handle the toggle independetly.
as for the style, they are 2 syntax working :
style={ !this.state.toggle ? {} : { color: "#EC1C24", backgroundColor: 'red' }}
or
style={[ this.state.toggle && {color: "#EC1C24", backgroundColor: 'red'} ]}
So first create a component for the circle button
export class CircleButton extends Component {
constructor(props) {
super(props);
this.state = {
toggled: false
};
}
render() {
return (
<TouchableOpacity
key={index}
onPress={() => this.toggle1(item)}
style={
!this.state.pressStatus
? styles.sizes
: styles.sizesAlt
}
onHideUnderlay={this.props.onHideUnderlay.bind(this)}
onShowUnderlay={this.props.onShowUnderlay.bind(this)}
>
<Text
key={index}
style={
!this.state.toggle ? {} : { color: "#EC1C24" }
}
>
{item}
</Text>
</TouchableOpacity>
);
}
}
And Change your View to something like this
<View style={{ paddingTop: 10, width: "49%" }}>
<View style={styles.sizeView}>
<View style={styles.sizeView3}>
<Text style={styles.chartText}>{t("size")}</Text>
</View>
</View>
<View style={styles.sizeButtons}>
{this.state.productsList[0].sizes.map((item, index) => {
return (
<CircleButton
onHideUnderlay={this._onHideUnderlay.bind(this)}
onShowUnderlay={this._onShowUnderlay.bind(this)}/>
);
})}
</View>
</View>

Categories

Resources