Problem with conditional rendering in React Native - react-native

I have a map and i want a marker to appear when the user long-presses on a point.
I have the following code:
const [coords, setcoords] = React.useState(getcoord());
const [show, setShow] = React.useState(false);
const setPointCoords = (e) => {
setcoords(e.geometry.coordinates);
setShow(!show);
}
return (
<View style={style.page}>
<MapboxGL.MapView
style={style.map}
rotateEnabled={false}
styleURL="mapbox://styles/daskalgg/ckp26fbmb34iv18otkav9sj4s"
onLongPress={(e) => {
setPointCoords(e);
}}
>
{
show &&
<MapboxGL.PointAnnotation
key="marker"
id="point"
draggable={true}
coordinate={coords} />
}
</MapboxGL.MapView>
{
show &&
<Text>test</Text>
}
</View>
);
The problem i have is that, although setPointCoords is called and the value of show changes, the marker never appears.
Some things i have noticed:
The Text element, which is there for testing purposes, works as expected.
The problem goes away when the initial value of show is true.

Try this out
{show ? (
<MapboxGL.PointAnnotation
key="marker"
id="point"
draggable={true}
coordinate={coords}
/> )
: null}

I found a solution but it's pretty hacky
<MapboxGL.PointAnnotation
key="marker"
id="point"
draggable={true}
onSelected={(e) => {
console.log(e);
}}
coordinate={coords} >
<View
key="marker"
style={{
borderColor: "black", backgroundColor: "red",
display: show ? 'flex' : 'none'
}}
>
<Text> This is a Marker </Text>
</View>
</MapboxGL.PointAnnotation>
Since i can't control the style of the PointAnnotation itself, i display a View and control it's visibility through 'show'. I am not accepting it as the correct answer yet in hopes there is a better one.

Related

Dynamically populating nested react-native-collapsible

I need to populate a menu with items from an api request.
I made some sample items; const items. Some of the menu items have children, and the children can also have children, so I need to nest several levels of menu items.
I made an Accordion() with Collapsible from react-native-collapsible and an AccordionItem() for items that have no children.
function App() renders the menu items twice. Once by manually adding Accordions and AccordionItems and once by mapping items[]. RootMenuItem() is called for each top level item and renders that item and its sub-items by recursion.
When manually adding each item it works the way I want it to. I need to populate the menu programatically, but nested accordions rendered by RootMenuItem() are misbehaving on android and iOS. When testing in Web on snack.io it seems to be working fine.
Here is a snack with my complete App.js:
https://snack.expo.dev/#dissar/nested-collapsibles
Am I doing something wrong?
Does anybody have any tips for doing this in a better way?
PS: The dynamically rendered items have weird widths when testing on snack.io, but don't worry about that.
I seem to have fixed it myself by removing the View on line 46 and 56;
function RootMenuItem({item}){
if(item.children.length > 0) {
return(
<View style={{flex: 1}} key={item.id}> // <---- I removed this
<Accordion item={item} style={styles.menuItemView}>
{
item.children.map(child => (
<View style={{paddingLeft: 18}} key={child.id}>
<RootMenuItem item={child} style={{paddingLeft: 10}}/>
</View>
))
}
</Accordion>
</View> // <---- Also removed this
)
}
else return (
<AccordionItem item={item}/>
)
}
Not really sure though why that View made the nested accordions not work as they should. Please let me know if you have the answer.
I have a better solution without using any 3rd party library. This is completely customised and easy to understand. I used the same format of data as you used.
first of all, we have a component
const [active, setActive] = useState(null);
return (
<ScrollView
style={{ marginTop: 50 }}
contentContainerStyle={styles.container}>
{arr.map((x, i) => (
<Item
key={x.name}
active={active}
i={i}
setActive={setActive}
child={x.child}
/>
))}
</ScrollView>
);
then for the list items and their child
function Item({ i, active, setActive, child }) {
const onPress = () => {
LayoutAnimation.easeInEaseOut();
setActive(i == active ? null : I);
};
const [childActive, setChildActive] = useState(null);
const open = active == I;
return (
<TouchableOpacity style={styles.item} onPress={onPress} activeOpacity={1}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text>Header - {i + 1}</Text>
{child.length ? <Text>{open ? 'close' : 'open'}</Text> : null}
</View>
{open &&
child.map((x, i) => {
if (x.child.length) {
return (
<Item
key={x}
active={childActive}
i={i}
setActive={setChildActive}
child={x.child}
/>
);
}
return (
<Text key={x} style={styles.subItem}>
- SOME DATA
</Text>
);
})}
</TouchableOpacity>
);
}
It's a completely dynamic process, you can extend the chain as long as you want. You can also check out the expo to look at its works.
https://snack.expo.dev/#akash0208/forlorn-popsicle

DoneButton is not rendering in 'react-native-app-intro-slider-rerender-on-prop-change'

I am trying to make a multiple choice test with a few questions using React Native package 'react-native-app-intro-slider'. The problem was that this package was not rerendering the items when something changes, state for example. I found a similar or refactored package called 'react-native-app-intro-slider-rerender-on-prop-change' that did the same thing but rerendered items on a state change. But the problem is that it does not render the Done button at last slide.
The last package i used was this :
import AppIntroSlider from 'react-native-app-intro-slider-rerender-on-prop-change';
The render method for the 'MCTContainer' (Multiple Choice Container) class is :
render() {
if (this.state.showRealApp) {
this.props.navigation.navigate('TopicGroupDetails', { topicGroup: this.state.topicGroup });
return null;
} else {
const questions = this.state.questions;
return (
<AppIntroSlider
renderItem={this._renderItem}
slides={questions}
onDone={this._onDone}
onSkip={this._onDone}
activeDotStyle={{ backgroundColor: '#039BE5' }}
showPrevButton
showNextButton
showSkipButton
showDoneButton
renderPrevButton={this._renderPrevButton}
renderNextButton={this._renderNextButton}
renderSkipButton={this._renderSkipButton}
renderDoneButton={this._renderDoneButton}
/>
)
}
}
and its renderDoneButton method is like following :
_renderDoneButton = () => {
return (
<View style={styles.buttonCircle}>
<Icon
name="check"
color="#039BE5"
size={24}
style={{ backgroundColor: 'transparent' }}
/>
</View>
);
};
This is actually the same, exactly the same as I used for the component 'SliderComponent' where I show tutorial introductory text in multiple slides. It renders the 'done' button fine. But the only differenence is that I used package 'react-native-app-intro-slider' there, not 'react-native-app-intro-slider-rerender-on-prop-change'.
Please help me if someone has encountered such a problem. Thanx in advance ...
I finally solved it.
I just added a condition on "renderItem" function of the "AppIntroSlider", that checks whether it is the last slide. If it is it renders a button that calls "AppIntroSlider"-s "_onDone" method that completes the slide show.
_renderItem = ({item}) => {
return(
{
(this.state.isLastSlide) ?
(
<TouchableHighlight
onPress={this._onDone}
>
<View>
<Text>
COMPLETE SLIDE SHOW
</Text>
<Icon
name="check"
color="white"
size={24}
/>
</View>
</TouchableHighlight>
) :
(<View></View>)
}
)
}

Card counting functionality react native

I am trying to make an e-commerce app and everything working only the cart icon counts not updating when the user adds an item to cart.
Here is my main page where i am calling component:
<Header headerTitle={this.state.wineD.name} lefticonType={'back'} navigation={this.props.navigation} />
Here is component code:
componentDidMount(){
//API code here and updating response count in state.
if(response.data.success){
this.setState({
cartItems: (response.data.data.cart.items != '' && (response.data.data.cart.items).length > 0)?
(response.data.data.cart.items).length : 0
})
this.props.changeLoaderStatus();
}
}
<FlatHeader
leftIcon={<Icon name={leftIcon} size={20} color="#FFF" />}
leftIconHandler={() => {
(this.props.lefticonType == 'bars' ?
this.props.navigation.dispatch(DrawerActions.openDrawer())
: goBack())
}}
centerContent={
<View style={{width: width*0.7,alignItems:'center'}}>
<Text numberOfLines={1} style={{ color: '#FFF',fontSize:22,fontWeight:'bold' }}>{this.props.headerTitle}</Text>
</View>
}
rightIcon={<Group><Icon name="shopping-cart" size={20} color="#FFF" />
<View style={{width:16,height:16,borderRadius:8,backgroundColor:'red',justifyContent:'center',
alignItems:'center',marginBottom:14}}>
<Text style={{fontSize:10,color:'#fff',fontWeight:'bold'}}>{this.state.cartItems}</Text></View></Group>}
rightIconHandler={() => this.props.navigation.navigate('Cart')}
large
style={{ backgroundColor: '#d7b655' }}
/>
This is the screen where from other component updating the cart
Anyone have solution please share here.
If I understood correctly your problem, you may need to modify this.setState in this way:
componentDidMount(){
//API code here and updating response count in state.
if(response.data.success){
this.setState({
cartItems: (response.data.data.cart.items != '' && (response.data.data.cart.items).length > 0)?
(response.data.data.cart.items).length : 0
},()=>{
this.props.changeLoaderStatus(); }) } }
try this and let me know if it works for you.

I want my app to not show articles or give them low priority to those that have already been seen

I have a basic article app like inshorts i am storing all the articles in database which i fetch on the opening and display them in card format.Now I want to implement that when a card id viewed it should get low priority and render at the end next time the app is opened on that mobile.
I have no clue how to implement this.
This is how i am currently rendering it
renderArtciles=()=>{
let len=this.state.dataSource.length;
return this.state.dataSource.map((item,i)=>{
this.state.id=item._id;
this.state.priority=item.priority;
this.state.views=item.views;
if (i == this.state.currentIndex-1)
{
return(
<Animated.View key={item._id} {...this.state.panResponder.panHandlers} style={this.state.swiped_pan.getLayout()}>
< View style={{ flex: 1,position:'absolute',height:height,width:width,backgroundColor:'white'}}>
< View style={styles.Imagebody}>
<Image source={{ uri:item.img.data }} style={styles.image} />
</View>
<View style={styles.inner}>
<Text>{item.body} i==={i}{this.state.currentIndex} </Text>
</View>
</View>
</Animated.View>
)
}
else if (i < this.state.currentIndex)
{
return null
}
if (i == this.state.currentIndex)
{
return(
<Animated.View key={item._id} {...this.state.panResponder.panHandlers} style={this.state.pan.getLayout()}>
< View style={{ flex: 1,position:'absolute',height:height,width:width,backgroundColor:'white'}}>
< View style={styles.Imagebody}>
<Image source={{ uri:item.img.data }} style={styles.image} />
</View>
<View style={styles.inner}>
<Text>{item.body} i==={i}{this.state.currentIndex} </Text>
</View>
</View>
</Animated.View>
)
}
else{
return(
<Animated.View key={item._id} >
< View style={{ flex: 1,position:'absolute',height:height,width:width,backgroundColor:'white'}}>
< View style={styles.Imagebody}>
<Image source={{ uri:item.img.data }} style={styles.image} />
</View>
<View style={styles.inner}>
<Text>{item.body} i==={i}{this.state.currentIndex} </Text>
</View>
</View>
</Animated.View>
)
}
}
).reverse()
}
You could make use of AsyncStorage to store which items have been viewed (and how many times) in a JSON object which you can increment every time an item is viewed, and then retrieve again when the app is opened (and store in some state variable). You can then work out your ordering/priority logic based on the number of views.
To store the items you would do something like this:
_storeData = async () => {
try {
var itemsJson = {items: [
{item_id: 'foo', view_count: 10},
{item_id: 'bar', view_count: 5}
]}
await AsyncStorage.setItem('ItemViews', JSON.stringify(itemsJson));
} catch (error) {
// Error saving data
}
};
And to retrieve the items on app open you would do something like this:
_retrieveData = async () => {
try {
const items = await AsyncStorage.getItem('ItemViews');
// Rest of your code
} catch (error) {
// Error retrieving data
}
};

How to set the textinput box above the Keyboard while entering the input field in react native

I am using react-native TextInput component. Here I need to show the InputBox above the keyboard if the user clicks on the textInput field.
I have tried below but i am facing the issues
1. Keyboard avoiding view
a. Here it shows some empty space below the input box
b. Manually I need to scroll up the screen to see the input field which I was given in the text field
c. Input box section is hiding while placing the mouse inside the input box
2. react-native-Keyboard-aware-scroll-view
a.It shows some empty space below the input box
b.ScrollView is reset to the top of the page after I moving to the next input box
Here I set the Keyboard-aware-scroll-view inside the ScrollView component
Kindly clarify
My example code is
<SafeAreaView>
<KeyboardAvoidingView>
<ScrollView>
<Text>Name</Text>
<AutoTags
//required
suggestions={this.state.suggestedName}
handleAddition={this.handleAddition}
handleDelete={this.handleDelete}
multiline={true}
placeholder="TYPE IN"
blurOnSubmit={true}
style= {styles.style}
/>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
[https://github.com/APSL/react-native-keyboard-aware-scroll-view]
Give your TextInput a position: absolute styling and change its position using the height returned by the keyboardDidShow and keyboardDidHide events.
Here is a modification of the Keyboard example from the React Native documentation for demonstration:
import React, { Component } from 'react';
import { Keyboard, TextInput } from 'react-native';
class Example extends Component {
state = {
keyboardOffset: 0,
};
componentDidMount() {
this.keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
this._keyboardDidShow,
);
this.keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
this._keyboardDidHide,
);
}
componentWillUnmount() {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
_keyboardDidShow(event) {
this.setState({
keyboardOffset: event.endCoordinates.height,
})
}
_keyboardDidHide() {
this.setState({
keyboardOffset: 0,
})
}
render() {
return <View style={{flex: 1}}>
<TextInput
style={{
position: 'absolute',
width: '100%',
bottom: this.state.keyboardOffset,
}}
onSubmitEditing={Keyboard.dismiss}
/>
</View>;
}
}
First of all, You don't need any extra code for Android platform. Only keep your inputs inside a ScrollView. Just use KeyboardAvoidingView to encapsulate the ScrollView for iOS platform.
Create function such as below which holds all the inputs
renderInputs = () => {
return (<ScrollView
showsVerticalScrollIndicator={false}
style={{
flex: 1,
}}
contentContainerStyle={{
flexGrow: 1,
}}>
<Text>Enter Email</Text>
<TextInput
style={styles.text}
underlineColorAndroid="transparent"
/>
</ScrollView>)
}
Then render them inside the main view as below
{Platform.OS === 'android' ? (
this.renderInputs()
) : (
<KeyboardAvoidingView behavior="padding">
{this.renderInputs()}
</KeyboardAvoidingView>
)}
I have used this method and I can assure that it works.
If it is not working then there is a chance that you are missing something.
Hooks version:
const [keyboardOffset, setKeyboardOffset] = useState(0);
const onKeyboardShow = event => setKeyboardOffset(event.endCoordinates.height);
const onKeyboardHide = () => setKeyboardOffset(0);
const keyboardDidShowListener = useRef();
const keyboardDidHideListener = useRef();
useEffect(() => {
keyboardDidShowListener.current = Keyboard.addListener('keyboardWillShow', onKeyboardShow);
keyboardDidHideListener.current = Keyboard.addListener('keyboardWillHide', onKeyboardHide);
return () => {
keyboardDidShowListener.current.remove();
keyboardDidHideListener.current.remove();
};
}, []);
You can use a scrollview and put all components inside the scrollview and add automaticallyAdjustKeyboardInsets property to scrollview.it will solve your problem.
automaticallyAdjustKeyboardInsets Controls whether the ScrollView should automatically adjust its contentInset and
scrollViewInsets when the Keyboard changes its size. The default value is false.
<ScrollView automaticallyAdjustKeyboardInsets={true}>
{allChildComponentsHere}
<View style={{ height: 30 }} />//added some extra space to last element
</ScrollView>
Hope it helps.
you can use KeyboardAvoidingView as follows
if (Platform.OS === 'ios') {
return <KeyboardAvoidingView behavior="padding">
{this.renderChatInputSection()}
</KeyboardAvoidingView>
} else {
return this.renderChatInputSection()
}
Where this.renderChatInputSection() will return the view like textinput for typing message. Hope this will help you.
For android you can set android:windowSoftInputMode="adjustResize" for Activity in AndroidManifest file, thus when the keyboard shows, your screen will resize and if you put the TextInput at the bottom of your screen, it will be keep above keyboard
react-native-keyboard-aware-scroll-view caused similar issue in ios. That's when I came across react-native-keyboard-aware-view. Snippets are pretty much same.
<KeyboardAwareView animated={true}>
<View style={{flex: 1}}>
<ScrollView style={{flex: 1}}>
<Text style={{fontSize: 20, color: '#FFFFFF'}}>A</Text>
<Text style={{fontSize: 20, color: '#FFFFFF'}}>B</Text>
<Text style={{fontSize: 20, color: '#FFFFFF'}}>C</Text>
<Text style={{fontSize: 20, color: '#FFFFFF'}}>D</Text>
</ScrollView>
</View>
<TouchableOpacity style={{height: 50, backgroundColor: 'transparent', alignItems: 'center', justifyContent: 'center', alignSelf: 'stretch'}}>
<Text style={{fontSize: 20, color: '#FFFFFF'}}>Submit</Text>
</TouchableOpacity>
</KeyboardAwareView>
Hope it hepls
You will definitely find this useful from
Keyboard aware scroll view Android issue
I really don't know why you have to add
"androidStatusBar": {
"backgroundColor": "#000000"
}
for KeyboardawareScrollview to work
Note:don't forget to restart the project without the last step it might not work
enjoy!
I faced the same problem when I was working on my side project, and I solved it after tweaking KeyboardAvoidingView somewhat.
I published my solution to npm, please give it a try and give me a feedback! Demo on iOS
Example Snippet
import React from 'react';
import { StyleSheet, TextInput } from 'react-native';
import KeyboardStickyView from 'rn-keyboard-sticky-view';
const KeyboardInput = (props) => {
const [value, setValue] = React.useState('');
return (
<KeyboardStickyView style={styles.keyboardView}>
<TextInput
value={value}
onChangeText={setValue}
onSubmitEditing={() => alert(value)}
placeholder="Write something..."
style={styles.input}
/>
</KeyboardStickyView>
);
}
const styles = StyleSheet.create({
keyboardView: { ... },
input: { ... }
});
export default KeyboardInput;
I based my solution of #basbase solution.
My issue with his solution that it makes the TextInput jumps up without any regard for my overall view.
That wasn't what I wanted in my case, so I did as he suggested but with a small modification
Just give the parent View styling like this:
<View
style={{
flex: 1,
bottom: keyboardOffset,
}}>
And it would work! the only issue is that if the keyboard is open and you scrolled down you would see the extra blank padding at the end of the screen.
android:launchMode="singleTask"
android:windowSoftInputMode="stateAlwaysHidden|adjustPan"
write these two lines in your android/app/src/main/AndroidManifest.xml
in activity tag
flexGrow: 1 is the key.
Use it like below:
<ScrollView contentContainerStyle={styles.container}>
<TextInput
label="Note"
value={currentContact.note}
onChangeText={(text) => setAttribute("note", text)}
/>
</ScrollView>
const styles = StyleSheet.create({
container: {
flexGrow: 1,
},
});
Best and Easy Way is to use Scroll View , It will Automatically take content Up and TextInput will not be hide,Can refer Below Code
<ScrollView style={styles.container}>
<View>
<View style={styles.commonView}>
<Image source={firstNameIcon} style={{width: 25, height: 25}}></Image>
<Text style={styles.commonTxt}>First Name</Text>
</View>
<TextInput
onFocus={() => onFocus('firstName')}
placeholder="First Name"
style={styles.txtInput}
onChangeText={(text) => onChangeText(text, 'firstName')}
value={firstNameValue}
/>
</View>
<View>
<View style={styles.commonView}>
<Image source={LastNameIcon} style={{width: 25, height: 25}}></Image>
<Text style={styles.commonTxt}>Last Name</Text>
</View>
<TextInput
onFocus={() => onFocus('lastName')}
placeholder="Last Name"
style={styles.txtInput}
onChangeText={(text) => onChangeText(text, 'lastName')}
value={lastNameValue}
/>
</View>
<View>
<View style={styles.commonView}>
<Image source={callIcon} style={{width: 25, height: 25}}></Image>
<Text style={styles.commonTxt}>Number</Text>
</View>
<TextInput
onFocus={() => onFocus('number')}
placeholder="Number"
style={styles.txtInput}
onChangeText={(text) => onChangeText(text, 'number')}
value={numberValue}
/>
</View>
<View>
<View style={styles.commonView}>
<Image source={emailIcon} style={{width: 25, height: 25}}></Image>
<Text style={styles.commonTxt}>Email</Text>
</View>
<TextInput
onFocus={() => onFocus('email')}
placeholder="Email"
style={styles.txtInput}
onChangeText={(text) => onChangeText(text, 'email')}
value={emailValue}
/>
</View>
<View style={styles.viewSavebtn}>
<TouchableOpacity style={styles.btn}>
<Text style={styles.saveTxt}>Save</Text>
</TouchableOpacity>
</View>
</ScrollView>
go to your Android>app>src>main> AndroidManifest.xml
write these 2 lines :
android:launchMode="singleTop" android:windowSoftInputMode="adjustPan"