React Native FlatList with clickable items - javascript

I'm building a custom line style for a react-native FlatList.
I want the user to navigate to item details when clicking on the line text, or navigate to another page (drill down next level) when clicked on the right caret, something like:
Here is my current list components code:
class MyList extends Component {
handleShowDetails = index => {
...
};
handleDrillDown = index => {
...
}
render = () => {
let data = // Whatever data here
return (
<View style={styles.container}>
<FlatList
data={data}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => (
<MyListItem
onTextPress={this.handleShowDetails}
onCaretPress={this.handleDrillDown}
item={item}
/>
)}
/>
</View>
);
};
}
export default MyList;
const styles = StyleSheet.create({
container: {
flex: 1
},
item: {
padding: 10,
fontSize: 18,
height: 44,
backgroundColor: colors.white,
borderStyle: "solid",
marginBottom: 1
}
});
And my list item component:
class MyListItem extends Component {
handleTextPress = () => {
if (this.props.onTextPress) this.props.onTextPress(this.props.item.id);
};
handleIconPress =() => {
if (this.props.onCaretPress) this.props.onCaretPress(this.props.item.id)
}
render = () => {
return (
<View style={styles.container}>
<View style={styles.textContainer}>
<Text onPress={this.handleTextPress}>{this.props.item.title}</Text>
</View>
<View style={styles.iconContainer}>
<Button onPress={this.handleIconPress}>
<Icon name="ios-arrow-forward"/>
</Button>
</View>
</View>
);
};
}
export default MyListItem;
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
backgroundColor: colors.white,
marginBottom: 1,
height: 30
},
textContainer: {
backgroundColor: colors.light,
paddingLeft: 5,
fontSize: 26
},
iconContainer: {
alignSelf: "flex-end",
backgroundColor: colors.primary,
paddingRight: 5,
fontSize: 26
}
});
Problems I'm facing:
a. Clicking on text is not working properly. Sometimes it navigates but most of time I cannot navigate, specially if I click on the empty space between the text and the caret.
b. I simply cannot style the fontSize of the text.
c. I'm not being able to space then accordingly.
d. I need to vertical center both itens on row.
Here is what I'm getting for now:

For the clicking issue, you could set an TouchableHighlight or a TouchableOpacity for the View.
For spacing and alignment issues, maybe you could set the text to flex: 9 and the icon to flex: 1 with FlexBox. Here are the docs for that https://facebook.github.io/react-native/docs/flexbox.html.

a. Try to make the text full the cell. I can see your text just in 50% of the cell. style <Text> with height: 44
b. fontsize has to be placed in the <Text> component. You are placing it in <View> component
c. "I'm not being able to space then accordingly". I can't get your point clearly.
d. Try justifyContent: 'center' in iconContainer

For part a I would suggest an improvement over Anthu's answer:
I you want the entire textContainer to be clickable I suggest using TouchableWithoutFeedback (or another Touchable which suits your needs) and wrap this around the textContainer View

Related

React Native, content going under navigation header, SafeAreaView not working

As you can see in the image below, I have to give some top margin in order to "not" hide my half of the content under the navigation header, isn't header supposed to be a "safe area" for the below content, to be on the safe side I provided the SafeAreaView but still my content goes under the Header and unfortunately I have to give some hardcoded margin top value to avoid hiding.
The above image is when I comment marginTop.
Above image is when I add marginTop: 70
Code:
NotificationScreen.tsx:
import {
View,
SafeAreaView,
Text,
StatusBar,
} from 'react-native';
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated';
import OrderItem from '../../components/OrderItem';
const NotificationScreen = () => {
const [orders, setOrders] = useState([]);
useEffect(() => {
// calling API...
}, []);
return (
<SafeAreaView style={styles.container}>
<StatusBar backgroundColor="transparent" translucent />
<Text style={{color: 'lightgray', fontSize: 18}}>My Orders: </Text>
<Animated.FlatList
data={orders}
entering={FadeIn}
leaving={FadeOut}
contentContainerStyle={{
alignItems: 'center',
}}
keyExtractor={(_: any, index) => 'key' + index}
renderItem={({item}) => <OrderItem key={item.id} data={item} />}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
marginTop: 70, // if I remove this, the content goes under the header as shown in image.
flex: 1,
padding: 10,
backgroundColor: COLORS.primary,
},
});
export default NotificationScreen;
One more query, why my OrderItem not taking the full width of FlatList (see image, the gray box is not takin the full width...), I have provided width: 100% to my OrderItem container:
OrderItem.tsx:
const OrderItem = ({data}) => {
return (
<View style={styles.container}>
<View style={styles.textBlock}>
<Text style={styles.label}>Strategy: </Text>
<Text style={styles.info}>{data.strategyTitle}</Text>
</View>
// ... same views as shown in image
</View>
);
};
const styles = StyleSheet.create({
container: {
width: '100%',
paddingVertical: 10,
paddingHorizontal: 10,
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: COLORS.lightGray,
borderRadius: 10,
},
textBlock: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
label: {
color: 'grey',
fontSize: 16,
},
info: {
color: 'white',
fontSize: 16,
},
});
export default OrderItem;
The SafeAreaView does not work currently for Android devices. You need to have something like this to avoid this issue:
import { Platform,StatusBar } from "react-native";
const styles = StyleSheet.create({
container: {
paddingTop: Platform.OS == "android" ? StatusBar.currentHeight : 0,
},
});
<SafeAreaView style={styles.container}>
</SafeAreaView>
And for the OrderItem not taking all available width, remove from Animated.FlatList this:
contentContainerStyle={{alignItems: 'center'}}

TextInput disappears under keyboard in React Native

I am making a react native app and for some reason when I tap on the text input to type something it disappears under the keyboard. I want it to come to the top of the keyboard like the way Instagram messenger and other messenger apps do it. I tried using keyboardAvoidView and setting the behavior to padding and height but none of it worked.
import {
View,
Text,
StyleSheet,
SafeAreaView,
TextInput,
TouchableOpacity,
} from "react-native";
import CommunityPost from "../components/CommunityPost";
import { Context as CommunityContext } from "../context/CommunityContext";
const Key = ({ navigation }) => {
const { createCommunityPost, errorMessage } = useContext(CommunityContext);
const [body, setBody] = useState("");
return (
<View style={styles.container}>
<SafeAreaView />
<CommunityPost />
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
multiline={true}
placeholder="Type..."
placeholderTextColor="#CACACA"
value={body}
onChangeText={setBody}
/>
<TouchableOpacity
style={{ justifyContent: "center", flex: 1, flexDirection: "row" }}
onPress={() => createCommunityPost({ body })}
>
<Text style={styles.sendText}>Send</Text>
</TouchableOpacity>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "flex-start",
backgroundColor: "#2B3654",
justifyContent: "flex-end",
},
inputContainer: {
justifyContent: "flex-end",
flexDirection: "row",
},
sendText: {
color: "white",
fontSize: 25,
},
input: {
fontSize: 25,
color: "white",
borderColor: "#2882D8",
borderWidth: 1,
borderRadius: 15,
width: "80%",
marginBottom: 30,
marginLeft: 10,
padding: 10,
},
});
export default Key;
You must KeyboardAvoidingView component provided by react native.
<KeyboardAvoidingView
behavior={Platform.OS == "ios" ? "padding" : "height"}
style={styles.container}
>
<SafeAreaView />
<CommunityPost />
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
multiline={true}
placeholder="Type..."
placeholderTextColor="#CACACA"
value={body}
onChangeText={setBody}
/>
<TouchableOpacity
style={{ justifyContent: "center", flex: 1, flexDirection: "row" }}
onPress={() => createCommunityPost({ body })}
>
<Text style={styles.sendText}>Send</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
For these situations we can use one of these methods:
1.wrapping Component with <ScrollView></ScrollView>
2.wrapping Component with <KeyboardAvoidingView></KeyboardAvoidingView>
Sometimes our wrong given styles can make these happens too such as : Having a fixed value for our styles, check your margins and use one of the given methods
I hope it helps
Try using keyboardVerticalOffset and test it with different values.
For more info check this out
For those who want to keep the code clean, just use the FlatList component and add a View component involving the component with the states: {flex: 1, height: Dimensions.get ("window"). Height}.
Below left a component ready for anyone who wants to use, just wrap your component in this way:
<FormLayout>
{... your component}
</FormLayout>
Here is the solver component:
import React from 'react'
import { View, StyleSheet, FlatList, Dimensions } from 'react-native'
import Background from './Background'
const FormLayout = ({ children }) => {
const renderContent = <View style={styles.container}>
{children}
</View>
return <FlatList
ListHeaderComponent={renderContent}
showsVerticalScrollIndicator={false}
/>
}
const styles = StyleSheet.create({
container: {
flex: 1,
height: Dimensions.get('window').height * 0.95
}
})
export default FormLayout

React native KeyboardAvoidingView not moving correctly

I have a TextInput that when pressed gets covered by the keyboard. So I wrapped it in a KeyboardAvoidingView. But regardless of the behavior that I set for this view, the TextInput won't move above the keyboard. Using position as the behavior moves the TextInput but only half way above the keyboard, while the other two don't seem to work at all.
I also tried wrapping my entire component with a KeyboardAvoidingView, but doing so breaks the entire layout.
Can anyone help me? I never managed to get KeyboardAvoidingView to work for me and now I really need it. Thanks in advance!
Here is my component. Also worth mentioning is that this component is top level(well, almost top level since it's wrapped in a Router)
const { height, width } = Dimensions.get('screen')
const style = StyleSheet.create({
main: {
height,
width,
flexDirection: 'column',
},
iconSelecter: {
width,
height: 196,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: Colors.primary,
marginTop: 32
},
icon: {
height: 164,
width: 164,
},
saveButton: {
width: 96,
height: 96,
borderRadius: 100,
backgroundColor: Colors.secondary,
alignItems: "center",
justifyContent: "center",
alignSelf: 'center',
position: 'absolute',
bottom: 96 + 32
},
saveIcon: {
height: 54,
width: 54,
},
textInputWrapper: {
borderBottomColor: Colors.textInputBorder,
width: 288,
borderBottomWidth: 1,
alignSelf: 'center',
marginTop: 96,
height: 48,
},
textInput: {
fontWeight: "300",
fontSize: 14,
margin: 0
},
hintWrapper: {
alignSelf: 'center',
marginTop: 4
},
hint: {
fontSize: 12,
fontFamily: "Roboto-Thin",
fontStyle: 'normal',
}
})
const CreateActivity = ({ goBack }: NavigationProps) => {
//////////////////////////////
//State and logic
///////////////
return (
// TODO: Add touchable opacity to dismiss keyboard
<View style={style.main}>
<Appbar title="New activity" canGoBack goBack={goBack} />
<View style={{ flex: 1 }}>
<View style={style.iconSelecter}>
<GestureRecognizer onSwipeLeft={nextIcon} onSwipeRight={lastIcon}>
<Image style={style.icon} source={icons[currentIconIndex]?.file}></Image>
</GestureRecognizer>
</View>
<View style={style.hintWrapper}>
<Text style={style.hint}>Swipe to cycle through the icons</Text>
</View>
<KeyboardAvoidingView>
<View style={style.textInputWrapper}>
<TextInput style={style.textInput} placeholder={"Give this activity a name"} value={name} onChangeText={setName}></TextInput>
</View>
</KeyboardAvoidingView>
<TouchableNativeFeedback onPress={createActivity} background={TouchableNativeFeedback.Ripple("#fff", true)}>
<View style={style.saveButton}>
<Image style={style.saveIcon} source={require("../../assets/icons/light/save.png")}></Image>
</View>
</TouchableNativeFeedback>
</View>
</View>
)
}
export default CreateActivity;
I suggest that you to try wrap all the content of the screen in <KeyboardAvoidingView /> (or make it one of the outermost elements), otherwise it only will slide up its children (the View and the TextInput) leaving the rest of the content in its original position, making the layout look overlaped and weird. If you do that, the value "position" should work fine.
Something like this:
<View style={style.main}>
<Appbar title="New activity" canGoBack goBack={goBack} />
<KeyboardAvoidingView behavior="position" >
<View style={{ flex: 1 }}> // --> Remove flex: 1 if you experience some issue with the positioning
<View style={style.iconSelecter}>
<GestureRecognizer onSwipeLeft={nextIcon} onSwipeRight={lastIcon}>
<Image style={style.icon} source={icons[currentIconIndex]?.file}></Image>
</GestureRecognizer>
</View>
<View style={style.hintWrapper}>
<Text style={style.hint}>Swipe to cycle through the icons</Text>
</View>
<KeyboardAvoidingView>
<View style={style.textInputWrapper}>
<TextInput style={style.textInput} placeholder={"Give this activity a name"} value={name} onChangeText={setName}></TextInput>
</View>
</KeyboardAvoidingView>
<TouchableNativeFeedback onPress={createActivity} background={TouchableNativeFeedback.Ripple("#fff", true)}>
<View style={style.saveButton}>
<Image style={style.saveIcon} source={require("../../assets/icons/light/save.png")}></Image>
</View>
</TouchableNativeFeedback>
</View>
</KeyboardAvoidingView>
</View>
Also see the comment in the code above. Check if you really need to use of flex: 1 in all the outer wrapper elements, and take a look to the height you are setting in the style.main based on dimentions. I don't think that it is necesary and I think it could lead to some measure issues if you fix the height of the parent container.
EDIT:
I was just digging in react-native docs and I realize that there is a zIndex that you could use to avoid ablsolute positioning. It is a relative style prop so it needs to be set between sibling views, like this:
export default class MyComponent extends React.Component {
render() {
return (
<View>
<View style={[styles.appbarShape, styles.appbarZIndex]} ><Text>Header</Text></View>
<KeyboardAvoidingView behavior="position" style={styles.contentZIndex}>
{other children}
<TextInput placeholder="enter text"/>
</KeyboardAvoidingView>
</View>
);
}
}
const styles = StyleSheet.create({
appbarShape: {
height: 80,
width: Dimensions.get('window').width,
justifyContent: 'center',
alignSelf: "stretch",
backgroundColor: "#FFF"
},
appbarZIndex: {
zIndex: 3,
},
contentZIndex: {
zIndex: 0
}
});
Since the view that represents the appbar has a greater zIndex it shows up over the ones with a lower zIndex
Check this out working in this snack https://snack.expo.io/5VXAcw4Y0
Docs: https://reactnative.dev/docs/layout-props
Hope it helps!
Use react-native-keyboard-aware-scroll-view
<KeyboardAwareScrollView extraHeight={135} enabledOnAndroid={true}
extraScrollHeight={70} style={styles.mainContainer}
automaticallyAdjustContentInsets={true}
enableOnAndroid={true}
keyboardShouldPersistTaps='handled'
scrollEnabled={true} >
//your form
</KeyboardAwareScrollView>
const styles = StyleSheet.create({
mainContainer: { flex: 1, marginHorizontal: 15, marginVertical: 15 },
});

React Native Add Spacing Between Components

I need help fixing the overlap of views in my react native app (Need spacing between them).
After pressing the plus sign twice in top right corner, the two Views end up overlapping without any space between them (Which are called HoldWorkout Components)
Image of overlap
My App.js contains:
let PRs = PRArray.map((val, key) => {
return (
<HoldWorkout
key={key}
keyval={key}
val={val}
exName={setName}
setsHold={setSets}
/>
);
});
PRs is contained in the following Scroll View on Return:
<View style={styles.container}>
<View style={styles.whiteColor}>
<TouchableOpacity
activeOpacity={0.5}
onPress={addPR.bind(this)}
style={styles.TouchableOpacity}
>
<Icon name="ios-add" color="purple" size={45} />
</TouchableOpacity>
<View style={styles.header}>
<Text style={styles.headerTitle}>Personal Records</Text>
</View>
</View>
<ScrollView style={styles.scrollViewStyle}>
<View style={styles.color}>{PRs}</View>
</ScrollView>
</View>
The Styles in App.js is:
const styles = StyleSheet.create({
whiteColor: {
backgroundColor: "white",
borderBottomColor: "#F0EFF5",
borderBottomWidth: 2,
height: 80
},
container: {
flex: 1,
borderBottomColor: "#F0EFF5",
borderBottomWidth: 2
},
color: {
marginTop: 20,
backgroundColor: "#F0EFF5"
},
});
In HoldWorkout.js on return I have
<View key={props.keyval} style={styles.boxWorkouts}>
<TextInput
style={styles.input2}
placeholder="Excercise Name"
placeholderTextColor="#a9a9a9"
onChangeText={props.exName}
/>
<ExSets weight={setWeights} rep={setRep} date={setDates} />
{sets}
<View style={styles.addSet}>
<Button title="Add Set" color="purple" onPress={addSets}></Button>
</View>
</View>
The Style for the View is style.boxWorkouts which is in HoldWorkout.js and looks like
const styles = StyleSheet.create({
boxWorkouts: {
borderColor: "#BFBFBF",
borderWidth: 1,
backgroundColor: "white",
height: 90
}
});
I tried adding marginBottom: 100 to styles.boxWorkouts but that is fixed amount and if I click "Add Set" button on one of the HoldWorkout Components then it will add another row which will increase the height of the component and end up overlapping the component underneath it.
Image of after clicking Add Set on first Hold Workout Component with marginBottom set to 100
Please help on telling me how to fix the spacing for this as I have been trying to figure it out for a while because I am not sure how to get the components pushed down when I click "Add Set" button on the components above. This will ensure it wont overlap no matter how many times "Add Set" is pressed on the HoldWorkouts Components Above.
You need to add empty row or empty div with spacing as below.
let PRs = PRArray.map((val, key) => {
return (
<div>
<HoldWorkout
key={key}
keyval={key}
val={val}
exName={setName}
setsHold={setSets}
/>
<div> <br/> </div>
</div>
);
});

Updating State not updating view

I am currently learning React Native.
I have built a view which should show a text Loading when the data is being fetched and should display the data once the data has been received by the app.
The app is hitting the backend server and is getting the URL(tried console.error on app and it does popup with ared screen showing the correct data details ).
The problem is the view is not being updated. Another thing that is weird is that the conditional view is showing the border which has been set for the container, but it does not display any text or data except that. Even if I put the correct data. Not sure if the condition is being validated correctly or not.
The border I mentioned is set in articleContainer in the stylesheet and is attached with the container which is with the condition this.state.hasResult
If i remove the style from that view the border dissappears obviously but even putting a static text inside the view is not being displayed(checked incase I parsed the json wrong.) Seems a bit confusing.
var constants = require("../constants")
var React = require('react');
var ReactNative = require('react-native');
var t = require('tcomb-form-native');
var authenticate = require("../services/authenticate")
var {
AppRegistry,
AsyncStorage,
StyleSheet,
Text,
View,
TouchableHighlight,
Alert,
ListView,
Image,
} = ReactNative;
const options = {}
class RecipePage extends React.Component{
constructor(props) {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1.id !== r2.id});
var getData = require("../services/get_featured");
var par = this;
var recipeId = props.recipeId;
this.state ={
hasResult: false,
noResult: false,
result: null,
isLoading: true,
}
getData(recipeId).then(function(data){
par.setState(
{
result:data,
hasResult:true,
noResult:false,
isLoading:false
}
)
}).catch(function(error) {
console.error(error);
});
}
goBack(){
this.props.navigator.pop();
}
render() {
return (
<View style={styles.container}>
<View style={styles.row}>
<Text style={styles.title}>Recipe</Text>
<TouchableHighlight onPress={this.goBack.bind(this)}>
<Image style={styles.back}
source={require("../static/images/back.png")}
/>
</TouchableHighlight>
</View>
{
this.state.hasResult &&
<View style={styles.article_container} >
<Text style={styles.article_title} >{this.state.result.id}</Text>
<Image style={styles.article_img}
source={{uri: this.state.result.image_link}}
/>
</View>
}
{
this.state.isLoading &&
<View style={styles.article_container} >
<Text style={styles.article_title} >Loading</Text>
</View>
}
</View>
);
}
}
var styles = StyleSheet.create({
container: {
justifyContent: 'center',
marginTop: 25,
padding: 20,
backgroundColor: '#ffffff',
},
title: {
fontSize: 30,
alignSelf: 'center',
marginBottom: 30
},
article_container: {
justifyContent: 'center',
marginTop: 5,
padding: 20,
backgroundColor: '#ffffff',
borderBottomColor: '#555555',
borderBottomWidth: 1,
flex:1
},
article_title: {
fontSize: 25,
alignSelf: 'center',
marginBottom: 3,
flex:2
},
article_img: {
width:200,
height:200,
alignSelf: 'center',
flex:1
},
article_description :{
fontSize:15,
flex:3
},
back:{
backgroundColor:'#ffffff',
width:20,
height:20
}
});
module.exports = RecipePage;
If you think I can improve the code not relating to this question, then feel free to comment and enlighten me :)
Edit:
I played around a bit more and found that if I change the attached styles as:-
<View style={styles.row} >
<Text style={styles.title} >{this.state.result.title}</Text>
<Image
source={{uri: this.state.result.image_link}}
/>
</View>
from
<View style={styles.article_container} >
<Text style={styles.article_title} >{this.state.result.id}</Text>
<Image style={styles.article_img}
source={{uri: this.state.result.image_link}}
/>
</View>
I am able to view the title after making this change, but image is still not displayed. And I am not sure why it was not showing for the other style.
Even if one of the style is attached -> article_title or article_container, its not being displayed.
Edit 2:
Even if I apply the style manually, it is not displayed like
<Text style={{fontSize: 25,alignSelf: 'center',marginBottom: 3,flex:2}} >{this.state.result.title}</Text>
I am navigating to this page from a parent which uses similar styles for a listview and it is showing there perfectly.
The problem seemed to be that I used flex property in styles without using ScrollView or ListView.
I would research more into that and update the answer.

Categories