How to present vertical separators on a horizontal `FlatList`? - react-native

I’m using React Native’s new FlatList component. I’m using it to present an horizontal list, but when using the built-in ItemRenderComponent it presents the separators beneath each items, instead of in between.
Is there a way to change that?
interface State {
dataSource: WhosOutByPolicy[];
}
interface Props {
data: WhosOutByPolicy[];
}
class WhosOutParentCell extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { dataSource: props.data };
}
renderSeparator = () => {
return (
<View
style={{
height: 100,
width: 3,
backgroundColor: "#D81458"
}}
/>
);
};
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.dataSource}
horizontal={true}
renderItem={({ item }) => (
<WhosOutUsersInPolicyCell data={item} />
)}
keyExtractor={item => item.policyDispalyName}
ItemSeparatorComponent={this.renderSeparator}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#EFEFEF"
}
});
export default WhosOutParentCell;

It is a react-native bug that is not fixed yet. You can rewrite your code as blow to solve this problem:
renderSeparator = () => {
return (
<View
style = {{
height: 100,
width: 3,
backgroundColor: '#D81458'
}}
/>
)
}
_renderItem = ({item, index}) => (
<View style={{flexDirection: 'row'}}>
<WhosOutUsersInPolicyCell data = { item } />
{(index !== this.state.dataSource.length - 1) && this.renderSeparator()}
</View>
)
render() {
return (
<View style = { styles.container } >
<FlatList
data = { this.state.dataSource }
horizontal = { true }
renderItem = {this._renderItem}
keyExtractor = { item => item.policyDispalyName }
/>
</View>
)
}

Related

How to use ScrollToIndex in React Native?

I use a FlatList to display search results.
<View>
<FlatList
data={this.state.films}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => <FilmItem film={item}/>}
/>
</View>
However, when the user starts a second search, this list does not restart to index 0. So I would like to add ScrollToIndex or viewPosition. I tried this but it doesn't work :
<View>
<FlatList
data={this.state.films}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => <FilmItem film={item}/>}
scrollToItem={(index) => {0}}
/>
</View>
Could you please explain me why this is wrong and what would be the best solution ?
Thanks a lot,
Try this... I made a bunch of changes and have put comments:
// Components/Search.js
import React from "react";
import {StyleSheet, View, TextInput, Button, Text, FlatList, ActivityIndicator} from "react-native";
import FilmItem from "./filmItem";
import {getFilmsFromApiWithSearchedText} from "../API/TMDBApi";
class Search extends React.Component {
flatListRef = null; // declaring this here to make it explicit that we have this available
constructor(props) {
super(props);
this.searchedText = "";
this.state = {
films: [],
isLoading: false,
};
}
_loadFilms = () => {
// needs to be an arrow function so `this` is bound to this component
this.scrollToIndex(); // assumed you meant to actually call this?
if (this.searchedText.length > 0) {
this.setState({isLoading: true});
getFilmsFromApiWithSearchedText(this.searchedText).then(data => {
this.setState({
films: data.results,
isLoading: false,
});
});
}
};
// needs arrow to use `this`
_searchTextInputChanged = text => {
this.searchedText = text;
};
// needs arrow to use `this`
_displayLoading = () => {
// better to return null if not loading, otherwise return loading indicator
if (!this.state.isLoading) return null;
return (
<View style={styles.loading_container}>
<ActivityIndicator size='large' />
</View>
);
};
scrollToIndex = () => {
// you previously had this inside another method
this.flatListRef && this.flatListRef.scrollToIndex({index: 1}); // checking it exists first
};
render() {
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder='Titre du film'
onChangeText={text => this._searchTextInputChanged(text)}
onSubmitEditing={this._loadFilms} // no need to create a new anonymous function here
/>
<Button title='Rechercher' onPress={this._loadFilms} />
<View>
<FlatList
data={this.state.films}
ref={ref => (this.flatListRef = ref)}
keyExtractor={item => item.id.toString()}
onEndReachedThreshold={1} // corrected typo
onEndReached={() => {
console.log("TOC");
}}
renderItem={({item}) => <FilmItem film={item} />}
/>
</View>
{this._displayLoading()}
</View>
);
}
}
const styles = StyleSheet.create({
main_container: {
marginTop: 60,
padding: 15,
flex: 1,
},
loading_container: {
position: "absolute",
left: 0,
right: 0,
top: 100,
bottom: 0,
alignItems: "center",
paddingTop: 50,
backgroundColor: "white",
},
textinput: {
height: 50,
borderColor: "#999999",
borderWidth: 1,
paddingLeft: 5,
},
});
export default Search;
Using this code as example:
import React, { Component } from 'react';
import { Text, View, FlatList, Dimensions, Button, StyleSheet } from 'react-native';
const { width } = Dimensions.get('window');
const style = {
justifyContent: 'center',
alignItems: 'center',
width: width,
height: 50,
flex: 1,
borderWidth: 1,
};
const COLORS = ['deepskyblue','fuchsia', 'lightblue '];
function getData(number) {
let data = [];
for(var i=0; i<number; ++i)
{
data.push("" + i);
}
return data;
}
class ScrollToExample extends Component {
getItemLayout = (data, index) => (
{ length: 50, offset: 50 * index, index }
)
getColor(index) {
const mod = index%3;
return COLORS[mod];
}
scrollToIndex = () => {
let randomIndex = Math.floor(Math.random(Date.now()) * this.props.data.length);
this.flatListRef.scrollToIndex({animated: true, index: randomIndex});
}
scrollToItem = () => {
let randomIndex = Math.floor(Math.random(Date.now()) * this.props.data.length);
this.flatListRef.scrollToIndex({animated: true, index: "" + randomIndex});
}
render() {
return (
<View style={styles.container}>
<View style={styles.header}>
<Button
onPress={this.scrollToIndex}
title="Tap to scrollToIndex"
color="darkblue"
/>
<Button
onPress={this.scrollToItem}
title="Tap to scrollToItem"
color="purple"
/>
</View>
<FlatList
style={{ flex: 1 }}
ref={(ref) => { this.flatListRef = ref; }}
keyExtractor={item => item}
getItemLayout={this.getItemLayout}
initialScrollIndex={50}
initialNumToRender={2}
renderItem={({ item, index}) => (
<View style={{...style, backgroundColor: this.getColor(index)}}>
<Text>{item}</Text>
</View>
)}
{...this.props}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
header: {
paddingTop: 20,
backgroundColor: 'darkturquoise',
alignItems: 'center',
justifyContent: 'center'
}
});
export default class app extends Component {
render() {
return <ScrollToExample
data={getData(100)}
/>
}
}
I tried to add a ref but it doesn't work :
// Components/Search.js
import React from 'react'
import { StyleSheet, View, TextInput, Button, Text, FlatList, ActivityIndicator } from 'react-native'
import FilmItem from './filmItem'
import { getFilmsFromApiWithSearchedText } from '../API/TMDBApi'
class Search extends React.Component {
constructor(props) {
super(props)
this.searchedText = ""
this.state = {
films: [],
isLoading: false,
}
}
_loadFilms() {
{this.scrollToIndex}
if (this.searchedText.length > 0) {
this.setState({isLoading: true})
getFilmsFromApiWithSearchedText(this.searchedText).then(data => {
this.setState({
films: data.results,
isLoading: false
})
})
}
}
_searchTextInputChanged(text) {
this.searchedText = text
}
_displayLoading() {
if (this.state.isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size='large'/>
</View>
)
}
scrollToIndex = () => {
this.flatListRef.scrollToIndex({index: 1});
}
}
render() {
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder='Titre du film'
onChangeText={(text) => this._searchTextInputChanged(text)}
onSubmitEditing={() => this._loadFilms()}
/>
<Button title='Rechercher' onPress={() => this._loadFilms()} />
<View>
<FlatList
data={this.state.films}
ref={(ref) => { this.flatListRef = ref; }}
keyExtractor={(item) => item.id.toString()}
onEndReachedThreashold={1}
onEndReached={() => {console.log("TOC")}}
renderItem={({item}) => <FilmItem film={item}/>}
/>
</View>
{this._displayLoading()}
</View>
)
}
}
const styles = StyleSheet.create({
main_container: {
marginTop: 60,
padding: 15,
flex: 1,
},
loading_container: {
position: 'absolute',
left: 0,
right: 0,
top: 100,
bottom: 0,
alignItems: 'center',
paddingTop: 50,
backgroundColor: 'white',
},
textinput: {
height: 50,
borderColor: '#999999',
borderWidth: 1,
paddingLeft: 5,
}
})
export default Search
I'm sur I am doing something wrong, but don't find the error. Thanks a lot for your help.

react-native FlatList scroll to bottom for chat app

I've made a chat app, and for rendering messages flatlist is used. But the problem is tried to scroll to the end of the screen every time the page is loaded, but it fails to do so. I've tried inverted props, but nothing happened, only the list got inverted.
Even played with ref to make it auto-scroll to the bottom, but nothing happened.
<FlatList
ref="flatList"
onContentSizeChange={() =>
this.refs.flatList.scrollToEnd()}
contentContainerStyle={{
marginBottom:
verticalScale(200)
}}
style={styles.list}
data={this.state.messages}
/>
How to make it scroll to the bottom the screen or scroll to the last index of the message when rendered?
(UPDATE)
IT WAS AN ISSUE WITH THE <Content/> component i used which belongs to native-base . Upon removing and replacing it with a <View/> it works perfectly fine.
Also, for chat based app the inverted prop in Flatlist is the way to implement in right way.
I've added the way i managed to scroll in the answer below. If you simply want your app to display the last item in the list and stays there, you can use inverted
You should use ref like this:
export default class MyAwesomeComponent extends React.Component {
FlatListRef = null; // add a member to hold the flatlist ref
render() {
return (
<FlatList
ref={ref => (this.FlatListRef = ref)} // assign the flatlist's ref to your component's FlatListRef...
onContentSizeChange={() => this.FlatListRef.scrollToEnd()} // scroll it
contentContainerStyle={{marginBottom: verticalScale(200)}}
style={styles.list}
data={this.state.messages}
/>
);
}
}
prueba esto
return (
<View style={{flex: 1}}>
<KeyboardAvoidingView
behavior="padding"
style={styles.keyboard}
keyboardVerticalOffset={height - 1000}>
<FlatList
ref={ref => (this.FlatListRef = ref)}
onContentSizeChange={() => this.FlatListRef.scrollToEnd()} // scroll it
// contentContainerStyle={{marginBottom: verticalScale(200)}}
// keyboardShouldPersistTaps='always'
style={styles.list}
extraData={this.state}
data={this.state.messages}
keyExtractor={item => {
return item.id;
}}
renderItem={e => this._renderItem(e)}
/>
<View style={styles.input}>
<TextInput
// style={{flex: 1}}
value={msg}
placeholderTextColor="#000"
onChangeText={msg => this.setState({msg: msg})}
blurOnSubmit={false}
onSubmitEditing={() => this.send()}
placeholder="Escribe el mensaje"
returnKeyType="send"
/>
</View>
</KeyboardAvoidingView>
</View>
);
You can use Javascript method to reverse to show your messages from end
messages.reverse()
scrollToListPosition = (index) => {
const itemOffset = this.getItemOffset(index)
this.flatListRef.scrollToOffset({ animated: false, offset: itemOffset })
}
getItemOffset = (index) => {
let heightsum = 0
for (i = 0; i < index; i++) {
heightsum = heightsum + this.itemHeight[i]
}
return heightsum
}
render(){
return (
<FlatList
ref={(ref) => { this.flatListRef = ref; }}
data={postList}
keyExtractor={(item, index) => item._id}
horizontal={false}
extraData={this.state}
keyboardShouldPersistTaps='always'
refreshing={this.props.isRefreshing}
onRefresh={this.handleRefresh}
onEndReached={this.handleLoadMore}
getItemLayout={(data, index) => (
{ length: this.getLength(index), offset: this.getLength(index) * index, index }
)}
renderItem={({ item, index }) => {
return (
<View onLayout={(event) => {
var { height } = event.nativeEvent.layout;
this.itemHeight[index] = height
}}
>
<ListCommon
key={index}
item={item}
index={index}
parentFlatList={this}
data={item}
instance={this.props.commanAction}
tag={this.state.tag}
changeRoute={this.props.changeRoute}
/>
</View>
);
}}
/>
)
}
getLength = (index) => {
if (this.itemHeight[index] === undefined) {
return 0;
}
return this.itemHeight[index]
}
Here is how i solved it:
export default class Test extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
setTimeout(() => {
this.FlatListRef.scrollToEnd();
}, 1500);
}
render() {
return (
<View style={{ flex: 1 }}>
<FlatList
data={[1, 2, 3, 4, 5, 6, 7, 8]}
ref={(ref) => (this.FlatListRef = ref)}
renderItem={({ item }) => {
return (
<View
style={{
height: 140,
width: 400,
backgroundColor: "yellow",
alignItems: "center",
justifyContent: "center",
}}
>
<Text>{item}</Text>
</View>
);
}}
/>
</View>
);
}
}

Implement onPress on Flatlist item

I am trying to send the data of the flatlist items when clicked and set to another class.The ontouch is working but I am having the error below in the image. Also how can I send the data of api to the other class and get from another class? I have implemented as follows:
export default class FlatSpeakers extends Component {
constructor(props) {
super(props);
this.state = { isLoading: true, data: [],selectedItem: null, }
const { navigate } = this.props.navigation;
}
onPressItem = () => {
navigate('SpeakersClick')
};
componentDidMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(res => {
this.setState({
isLoading: false,
data: res.data,
})
})
}
renderItem({ item }) {
return (
<TouchableOpacity onPress={()=>this.onPressItem(item)} >
<Card>
<CardSection>
<View style={styles.thumbnailContainerStyle}>
<Image
style={styles.thumbnailStyle}
source={{ uri: item.image }}
/>
</View>
<View style={styles.headerContentStyle}>
<Text style={styles.headerTextStyle}>{item.title}</Text>
<Text>{item.artist}</Text>
</View>
</CardSection>
</Card>
</TouchableOpacity>
)
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, padding: 20 }}>
<ActivityIndicator />
</View>
)
}
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
renderItem={this.renderItem}
keyExtractor={(item, index) => index}
onPress={this.onPressItem}
/>
</View>
);
}
}
Problem in your code is, that you are calling same method from two sides - on one side you are passing arguments in it on another side you are not passing arguments. If you wan't to have both cases covered you should change you onPressFunction, to accept arguments - in your case item:
onPressItem = (item) => {
navigate('SpeakersClick')
};
try to put this.onPressItem = this.onPressItem.bind(this) on constructor(props)

React Native FlatList Touchable Opacity

I used FlatList to display the title that is in the data. I added in TouchableOpacity for the FlatList. So for example, when I click 'First', I want to show the the data from 'mood' and it will show the list of passionate,rousing and confident. I want to show the list on a new file/screen and show purely the mood of list.
This is my data:
const Mock =
[
{ title: 'First',
mood:
[
{name: 'passionate'},
{name: 'rousing'},
{name: 'confident'},
],
},
{ title: 'Second',
mood:
[
{name: 'rollicking'},
{name: 'cheerful'},
{name: 'fun'},
{name: 'sweet'},
{name: 'amiable'},
{name: 'natured'}
],
This is my FlatList code:
export default class Cluster1 extends Component{
render() {
return (
<View>
<FlatList
data={Mock}
renderItem={({ item, index }) => {
return <FlatListItem item={item} index={index} />;
}}
/>
</View>
);
}
}
class FlatListItem extends Component {
render() {
return (
<View style={styles.list}>
<View>
<TouchableOpacity>
<Text style={styles.itemTitle}>{this.props.item.title}</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
What should I do with the TouchableOpacity when I want to show the mood name when I click the title?
This is my style code
const styles = StyleSheet.create({
itemTitle:{
fontSize: 25,
fontWeight: 'bold',
color: 'white',
margin: 20,
},
},
list:{
flex: 1,
backgroundColor: '#00BCD4',
alignItems: 'center',
justifyContent: 'space-between',
flexDirection: 'row',
},
});
You should modify your code as below to do this:
export default class Cluster1 extends Component {
render() {
return (
<View style={{ margin: 30, backgroundColor: '#ddd' }}>
<FlatList
data={Mock}
renderItem={({ item, index }) => {
return <FlatListItem item={item} index={index} />;
}}
/>
</View>
);
}
}
class FlatListItem extends Component {
state = { showItemIndex: [false, false] };
_onPress = index => () => {
let showItemIndex = this.state.showItemIndex;
showItemIndex[index] = !this.state.showItemIndex[index];
this.setState({ showItemIndex });
};
render() {
return (
<View style={styles.list}>
<View>
<TouchableOpacity onPress={this._onPress(this.props.index)}>
<Text style={styles.itemTitle}>{this.props.item.title}</Text>
</TouchableOpacity>
{this.state.showItemIndex[this.props.index] && (
<FlatList
data={this.props.item.mood}
extraData={this.state.showItemIndex}
renderItem={({ item, index }) => {
return (
<Text item={item} index={index}>
{item.name}
</Text>
);
}}
/>
)}
</View>
</View>
);
}
}
Use this, it's should be work fine for you:
Link: https://github.com/oblador/react-native-collapsible
Link: https://github.com/naoufal/react-native-accordion
Link: https://github.com/cuiyueshuai/react-native-expandable-section-flatlist
You could set a state variable which gets updated whenever your TouchableOpacity gets pressed. And then you conditionally render the title or the list of mood names:
class FlatListItem extends Component {
constructor(props) {
super(props);
this.state = {collapsed: true}
}
render() {
return (
<View style={styles.list}>
<View>
<TouchableOpacity
onPress={this.onPress}
>
<Text style={styles.itemTitle}>
{this.props.item.title}
</Text>
</TouchableOpacity>
{this.state.collapsed ?
<View /> :
<View>
{this.props.item.mood.map(mood => <Text>{mood.name}</Text>)}
</View>}
</View>
</View>
);
}
onPress = () => {
this.setState({collapsed: !this.state.collapsed})
}
}
You can do this by separating out the datasource that is required to show in the list.
First to display title: you can do something like this
export default class Cluster1 extends Component {
state = {
data: []
};
componentWillMount() {
const titles = Mock.forEach(data => data.title);
this.setState({
data: titles
});
}
onItemClick = item => {
const itemIndex = Mock.findIndex(data => (
data.title === item
));
const values = Mock[itemIndex].mood;
this.setState({
data: values
});
};
render() {
return (
<View>
<FlatList
data={this.state.data}
renderItem={({ item, index }) => {
return (
<FlatListItem
item={item}
index={index}
onItemClick={this.itemClick}
/>
);
}}
/>
</View>
);
}
}
and then in your FlatlistItem code:
class FlatListItem extends Component {
render() {
return (
<View style={styles.list}>
<View>
<TouchableOpacity onPress={() =>
this.props.onItemClick(this.props.item)
}>
<Text style={styles.itemTitle}>
{
this.props.item.name ?
this.props.item.name : this.props.item
}
</Text>
</TouchableOpacity>
</View>
</View>
);
}
}

React Native FlatList won't render

I'm trying to implement a FlatList in my React Native application, but no matter what I try the darn thing just won't render!
It's intended to be a horizontal list of images that a user can swipe through, but at this point I'd be happy to just get a list of text going.
Here's the relevant code. I can confirm that every render function is called.
render() {
const cameraScreenContent = this.state.hasAllPermissions;
const view = this.state.showGallery ? this.renderGallery() : this.renderCamera();
return (<View style={styles.container}>{view}</View>);
}
renderGallery() { //onPress={() => this.setState({showGallery: !this.state.showGallery})}
return (
<GalleryView style={styles.overlaycontainer} onPress={this.toggleView.bind(this)}>
</GalleryView>
);
}
render() {
console.log("LOG: GalleryView render function called");
console.log("FlatList data: " + this.state.testData.toString());
return(
<FlatList
data={this.state.testData}
keyExtractor={this._keyExtractor}
style={styles.container}
renderItem={
({item, index}) => {
this._renderImageView(item, index);
}
}
/>
);
}
}
_keyExtractor = (item, index) => index;
_renderImageView = (item, index) => {
console.log("LOG: renderItem: " + item);
return(
<Text style={{borderColor: "red", fontSize: 30, justifyContent: 'center', borderWidth: 5, flex: 1}}>{item}</Text>
);
}
//testData: ["item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item9"]
I'm fairly confident this isn't some flex issue, but in case I missed something here's the relevant stylesheets too:
const styles = StyleSheet.create({
container: {
flex: 1
},
overlaycontainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between'
},
});
So, what I'm expecting to happen is to see a list of text items.
What's happening instead is I see a white screen with nothing on it.
Why is this list not rendering?
You can try replicating this if you want, define FlatList inside your return().
<View>
<FlatList
data={Items}
renderItem={this.renderItems}
enableEmptySections
keyExtractor={item => item.id}
ItemSeparatorComponent={this.renderSeparator}
/>
</View>
Then declare your Items inside constant(array of objects) outside your class like this,
const Items = [
{
id: FIRST,
title: 'item1',
},
{
id: SECOND,
title: 'item2',
},
{
id: THIRD,
title: 'item3',
},
// and so on......
];
After this get your items outside render by calling a function
onSelection = (item) => {
console.log(item.title)
}
renderItems = ({ item }) => {
return(
<TouchableOpacity
onPress={() => this.onSelection(item)}>
<View>
<Text>
{item.title}
</Text>
</View>
</TouchableOpacity>
)
}
Are you not missing the return on your renderItem?
render() {
console.log("LOG: GalleryView render function called");
console.log("FlatList data: " + this.state.testData.toString());
return(
<FlatList
data={this.state.testData}
keyExtractor={this._keyExtractor}
style={styles.container}
renderItem={
({item, index}) => {
return this._renderImageView(item, index);
}
}
/>
);
}
}