the library quotes the getCurrentIndex function, but I can't implement it in my code. Could someone show me an example of use? I couldn't find it anywhere.
You need to pass a ref via the ref prop to the component. This will give you access to the getCurrentIndex function.
Here is a minimal example.
const data = [0, 1, 2, 3, 4]
const App = () => {
const scrollRef = React.useRef(null);
return <View style={styles.container}>
<SwiperFlatList
ref={scrollRef}
data={data}
renderItem={({ item }) => (
<TouchableOpacity onPress={() => console.log(scrollRef.current.getCurrentIndex())} style={{width: 200, height: 200, backgroundColor: "red"}}>
</TouchableOpacity>
)}
/>
</View>
};
The above will print the current index onPress of the rendered item. The key part is scrollRef.current.getCurrentIndex().
Related
I'm trying to make a layout so that the later parts of the view are only reachable by scrolling.
Currently I'm using Dimensions to generate Views with the correct height. Is there a better way of doing so? My current solution doesn't seem too correct.
export default function MyApp() {
const height = Dimensions.get('window').height;
return (
<View style={styles.container}>
<ScrollView>
<View style={{backgroundColor: 'green', height:height}}/>
<View style={{backgroundColor: 'red', height:40}}/>
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container:{
backgroundColor: 'white',
flex: 1
}
});
You can use VirtualizedList component, for example as
<VirtualizedList
data={['body']}
renderItem={({ item }) => (
<View style={styles.screen}>
{/* Put more content for body */}
</View>
)}
keyExtractor={(item, index) => index.toString()}
getItemCount={() => {
return 1;
}}
getItem={(data, index) => {
return data[index];
}}>
</VirtualizedList>
Your solution work, but not good and it have downside, when you change your phone orientation to landscape there will be bug. I dont like using Dimensions in my code unless there is no other way or use Dimensions addEventListener to listen window size and update component whenever window size change. I will suggest you a better way.
First, create a component called LayoutSizeAwareView, after this view rendered, we will catch it size from onLayout props and use them to render it children.
const LayoutSizeAwareView = (props) => {
const [size, setSize] = React.useState({width: 0, height: 0});
return (
<View
...props,
onLayout={(e) => {
setSize({
width: e.nativeEvent.layout.width,
height: e.nativeEvent.layout.height,
})
props.onLayout(e)
}}
>
{props.children(size)}
</View>
)
}
And then, in your case, use it like this
export default function MyApp() {
return (
<LayoutSizeAwareView style={styles.container}>
{({width, height}) => {
return (
<ScrollView>
<View style={{backgroundColor: 'green', height: height}}/>
<View style={{backgroundColor: 'red', height: 40}}/>
</ScrollView>
)
}}
</View>
);
}
const styles = StyleSheet.create({
container:{
backgroundColor: 'white',
flex: 1
}
});
This way your code look even cooler, there will be some typo in my code since I dont have IDE here, but you might get the idea.
I'm new to react-native. I am building a calculator app through a tutorial where I'm at the point right now where I'm trying to set it up where pressing one of the calculator buttons will log to the console that digit or symbol.
When I press one of the buttons on the calculator, I receive an error in the console 'Cannot read property '0' of undefined' if I click the 1,4,7 buttons but does 1 and 2 as well for 2,5,8 and 3,6,9 buttons respectively. I figure this means that this.buttonPressed isn't able to process my horizontal array for whatever reason, or that there is some other problem.
I've tried binding buttonPressed in the constructor with
this.buttonPressed = this.buttonPressed.bind(this)
to no avail.
export default class App extends Component {
constructor(){
super()
this.state = {}
}
buttonPressed(text) {
console.log(text)
}
render() {
let rows = []
let nums = [[1, 2, 3], [4, 5, 6], [7, 8, 9,], ['.', 0, '=']]
for(i=0; i<4; i++){
let row = []
for(let j=0; j<3; j++){
row.push(
<TouchableOpacity onPress={() => this.buttonPressed(nums[i]
[j])} style={styles.btn}>
<Text style={styles.btnText}>{nums[i][j]}</Text>
</TouchableOpacity>
)
}
rows.push(<View style={styles.row}>{row}</View>)
}
...
return (
<View style={styles.container}>
...
<View style={styles.buttons}>
<View style={styles.numbers}>
{rows}
</View>
</View>
</View>
);
}
}
Use ES6 format.
change:
buttonPressed(text) {
console.log(text)
}
to:
buttonPressed = (text) => {
console.log(text)
}
A better way to approach this problem would be to remove the logic from the render function. This would help when debugging the problem.
The rows/numbers array can be taken outside of the component as it will not change, so no need to recreate it each time the component render function is called. You can then use the map function on the array to render each row and number item. This is the preferred approach when working with react.
You can try the code below, and see if it works. Although there is some code missing from your snippet.
const ROWS = [[1, 2, 3], [4, 5, 6], [7, 8, 9,], ['.', 0, '=']]
export default class App extends Component {
buttonPressed(text) {
console.log(text)
}
render() {
return (
<View style={styles.container}>
<View style={styles.buttons}>
<View style={styles.numbers}>
{ROWS.map(numbers => (
<View style={styles.row}>
{numbers.map(number => (
<TouchableOpacity style={styles.btn} onPress={() => this.buttonPressed(number)}>
<Text style={styles.btnText}>{number}</Text>
</TouchableOpacity>
))}
</View>
))}
</View>
</View>
</View>
);
}
}
An even better approach would be to use a pure component. Then if you need state, you can use react hooks.
const ROWS = [[1, 2, 3], [4, 5, 6], [7, 8, 9,], ['.', 0, '=']]
const App = () => {
const buttonPressed = (text) => {
console.log(text)
}
return (
<View style={styles.container}>
<View style={styles.buttons}>
<View style={styles.numbers}>
{ROWS.map(numbers => (
<View style={styles.row}>
{numbers.map(number => (
<TouchableOpacity style={styles.btn} onPress={() => buttonPressed(number)}>
<Text style={styles.btnText}>{number}</Text>
</TouchableOpacity>
))}
</View>
))}
</View>
</View>
</View>
);
}
export default App
I followed this answer to dynamically style my component.
Here is my render method :
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.images}
numColumns={2}
keyboardShouldPersistTaps={'always'}
keyboardDismissMode={'on-drag'}
keyExtractor={item => item.localIdentifier}
renderItem={({ item, index }) =>
<TouchableHighlight
underlayColor='transparent'
onPress={() => this.openImage(index)}
onLongPress={() => this.startSelection(item)}
>
<View style={[styles.albumContainer, (this.state.selectedItems.indexOf(item)>-1)?styles.selectedItem:styles.unselectedItem]}>
<Image
style={styles.albumThumbnail}
source={item.image}
/>
</View>
</TouchableHighlight>
}
/>
</View>
);
}
As you can see I am displaying image thumbnail with TouchableHighlight and FlatList. When user will press and hold on any image thumbnail I called startSelection() with particular flatlist item which then add that item to state. I used that state to set style dynamically of my image as :
<View style={[styles.albumContainer, (this.state.selectedItems.indexOf(item)>-1)?styles.selectedItem:styles.unselectedItem]}>
<Image
style={styles.albumThumbnail}
source={item.image}
/>
</View>
Here is startSelection() method :
startSelection(item) {
let temp = this.state.selectedItems;
temp.push(item);
this.setState({
selectedItems : temp
});
}
Here is my stylesheet :
const styles = StyleSheet.create({
selectedItem: {
borderWidth: 3,
borderColor: '#22aaff',
},
unselectedItem: {
borderColor: '#000000',
}
});
But when user press and hold that view, item will added to state but style is not changing.
Please help me what's going wrong here !!!
This can be found on FlatList docs:
This is a PureComponent which means that it will not re-render if props remain shallow-equal. Make sure that everything your renderItem function depends on is passed as a prop (e.g. extraData) that is not === after updates, otherwise your UI may not update on changes. This includes the data prop and parent component state.
So you can add extraData to your FlatList component like this:
FlatList Component:
<FlatList
data={this.state.images}
extraData={this.state} //add this!
numColumns={2}
keyboardShouldPersistTaps={'always'}
keyboardDismissMode={'on-drag'}
keyExtractor={item => item.localIdentifier}
renderItem={({ item, index }) =>
<TouchableHighlight
underlayColor='transparent'
onPress={() => this.openImage(index)}
onLongPress={() => this.startSelection(item)}
>
<View style={[styles.albumContainer, (this.state.selectedItems.indexOf(item)>-1)?styles.selectedItem:styles.unselectedItem]}>
<Image
style={styles.albumThumbnail}
source={item.image}
/>
</View>
</TouchableHighlight>
}
/>
P.S: If your component state has variables which should not re-render FlatList, you would be better of using extraData = {this.state.selectedItems}, but then you need to make sure you pass a different reference to selectedItems when you call setState on startSelection. Like this:
startSelection(item) {
let temp = [...this.state.selectedItems];
temp.push(item);
this.setState({
selectedItems : temp
});
}
Wrap them with extra []
style={[styles.albumContainer, [(this.state.selectedItems.indexOf(item)>-1)?styles.selectedItem:styles.unselectedItem]]}
Working with React Native, having some issues with the FlatList component.
This is my FlatList
<FlatList
data={this.state._data}
renderItem={() => this.renderItem()}
refreshControl={
<RefreshControl
onRefresh={() => this.handleRefresh}
refreshing={this.state.refreshing}
/>
}
/>
This is my renderItem function:
renderItem({item, index}) {
return (
<View style={{marginTop: 10, marginHorizontal: 10, paddingLeft:
10}}>
<ListItem
roundAvatar
title={`${item.itemName}`}
subtitle={`${item.amount}`}
avatar={require('../../../images/logo.png')}
/>
<View
style={{
paddingBottom: 10,
paddingTop: 10,
display: 'flex',
flexDirection: "row",
justifyContent: "space-around",
alignContent: "center"
}}
>
<View style={{ flexDirection: "row", alignContent:
"center", width:"45%"}}>
<Button
block
small
// disabled={this.state.acceptButtonGray}
style=
{this.state.acceptButtonGray ? ({
backgroundColor: 'gray',
width: "100%"
}) : ({backgroundColor: "#369ecd",
width: "100%"
})}
onPress={() =>
this.setState({
modalVisible: true,
// acceptOrDeclineModalText: `Accept offer for ${item.amount} ${'\b'} Are you Sure?`,
acceptOffer: true,
})
}
>
<Text>
Accept
</Text>
</Button>
</View>
</View>
</View>
);
}
this.setState in the onPress in the button should make a Modal visible, and set acceptOffer to true. Modal opens, user confirms their offer. The offer button which opened that modal now should be gray, and even better, disabled.
Passing my RenderItem function as shown above, I receive
TypeError: Cannot read property 'item' of undefined.
Passing my RenderItem function like this:
renderItem={this.renderItem}
I Get This Error:
_this2.setState is not a function
The FlatList Component is certainly responsible for part of my issue, as well as how and where I am calling this.setState. Only one button is shown in my post, but there are two, one for accept, one for decline. Would having two modals change anything?
The FlatList displays my ListItem components with ease until I attempt to call this.setState in the buttons inside the View which contains those ListItems.
The Modal close button takes this.state.acceptOffer and if true, sets this.state.acceptButtonGray to true, should this logic be somewhere else?
Is there another way to open a modal and change the button color without using component state? Does react want these buttons inside of a TouchableOpacity?
I greatly appreciate any help given.
you should write a renderItem function like this
renderItem = ({item, index}) => {
// return here
}
Change your renderItem method to renderItem={this.renderItem.bind(this)}?
As per my Knowledge item and index are passed as object in flatlist's renderItem
so we can pass by two ways
1 way
Flatlist Component
<FlatList
data={this.state.data}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => this._renderItem(item, index)} //Passing as object from here.
/>
Render Item
_renderItem = (item, index) => {
console.log(item)
console.log(index)
}
2 way
Flatlist Component
<FlatList
data={this.state.data}
keyExtractor={(item, index) => index.toString()}
renderItem={( item, index ) => this._renderItem(item, index)}
/>
Render Item
_renderItem = ({item, index}) => { // Passing as object from here
console.log(item)
console.log(index)
}
1) You can write function as -
renderItem = ({item, index}) => {
// return here
}
2) or else if you want to execute your function then -
<FlatList
data={this.state._data}
renderItem={(item) => this.renderItem.bind(this, item)}
refreshControl={
<RefreshControl
onRefresh={() => this.handleRefresh}
refreshing={this.state.refreshing}
/>
}
/>
You have to use bind(this,item) or change function like (item)=>.
I experienced the same issue and wasted many hours to figure out why it was not re-rendering:
We need to set extraData prop of FlatList if there is any change in the state like so:
<FlatList data={this.state.data} extraData={this.state} .../>
Please see the official documentation here:
https://facebook.github.io/react-native/docs/flatlist.html
FlatList has numColumns support. How to set numColumns with SectionList?
Github issue: SectionList renderItem multi item support #13192
Here is my solution to numColumns for SectionList. If you have better let me know please.
class Example extends Component {
static propTypes = {
numColumns: PropTypes.number
};
static defaultProps = {
numColumns: 2
};
_renderSection = data => <Section {...data} />;
_renderItem = ({ section, index }) => {
const { numColumns } = this.props;
if (index % numColumns !== 0) return null;
const items = [];
for (let i = index; i < index + numColumns; i++) {
if (i >= section.data.length) {
break;
}
items.push(<Item item={section.data[i]} />);
}
return (
<View
style={{
flexDirection: "row",
justifyContent: "space-between"
}}
>
{items}
</View>
);
};
render() {
return (
<SectionList
sections={dumyData}
style={styles.container}
renderItem={this._renderItem}
renderSectionHeader={this._renderSection}
/>
);
}
}
It is possible to use FlatList with numColumns prop as the renderItem of SectionList.
const data = [ //Notice [[...]] instead of [...] as in the RN docs
{data: [[...]], title: ...},
{data: [[...]], title: ...},
{data: [[...]], title: ...},
]
render () {
return (
<SectionList
renderItem={this._renderSectionListItem}
renderSectionHeader={this._renderSectionHeader}
sections={data}
/>
)
}
renderSectionListItem = ({item}) => {
return (
<FlatList
data={item}
numColumns={3}
renderItem={this.renderItem}
/>
)
}
Digging this issue up, I came with a solution similar to Pir Shukarullah Shah 's.
I'm using FlatList instead of my regular item, taking into account only the first item in <SectionList/>'s renderItem method.
_renderList = ({ section, index }) => {
if (index !== 0) return null;
return (
<FlatList numColumns={columns}
columnWrapperStyle={styles.container}
data={section.data}
renderItem={this._renderItem}
keyExtractor={keyExtractor}
/>
)
}
...
<SectionList
renderItem={this._renderList}
renderSectionHeader={this._renderSectionHeader}
sections={itemList}
keyExtractor={keyExtractor}
/>
I found there is a simple solution. Please try adding the following property to the
contentContainerStyle={{
flexDirection : 'row',
justifyContent : 'flex-start',
alignItems : 'flex-start',
flexWrap : 'wrap'
}}
Besides, set and render the Section Header with the Width equal to the SectionList width. Otherwise, the list items will be displayed following the Section Header in row direction.
const DATA = [
{
renderItem: ({ item, index }) => {
return (<View style={{flexDirection:'row', alignItems:'center', justifyContent:'space-between', }}>
{item.map((elem,index)=>(<View style={{ borderColor: 'black', borderWidth: 2, minWidth:100 }}>
<Text>{elem.value}</Text>
</View>))
}
</View>);
},
data: [
[{id:'1', value:'Pizza'}, {id:'2', value:'Burger'}, {id:'3', value:'Onion Rings'}], //this array length will be noOfColumns
[{id:'4', value:'Risotto'}, {id:'5', value:'French Fries'}, {id:'6', value:'Water'}],
],
},
<SectionList
ref={listRef}
sections={DATA}
keyExtractor={_keyExtractor}
/>
I had the same logic like Pir Shukarullah Shah. The idea of using flexWrap is not recommended by react and warns to use numColumns prop in flatlist. If anyone has a better solution please add.
let items = []
const renderItem = ({ item, index }) => {
if (index % 2 === 0) {
items = []
items.push(<Card cloth={item} index={index} />)
return (index === clothes[0].data.length - 1) ? <View style={styles.row}>{items}</View> : null
}
items.push(<Card cloth={item} index={index} />)
return (
<View style={styles.row}>
{items}
</View>
)
}
The section list is :
<SectionList
sections={clothes}
renderItem={renderItem}
keyExtractor={(item, index) => index}
renderSectionHeader={renderSectionHeader}
stickyHeaderHiddenOnScroll={true}
stickySectionHeadersEnabled={true}
onEndReached={endReachedHandler}
onEndReachedThreshold={0.25}
contentContainerStyle={{ paddingBottom: '25%' }}
/>
The structure for clothes is:
let one = {name: 'Jeans pant'}
let many = Array(10).fill(one) // creating more dummy clothes
let cl = [{data: many, title: 'Cloth'}]
let [clothes, setClothes] = useState(cl)
I needed only one section so in cl array I wrote only one object initially if you want to have multiple sections you would need to add to the clothes array.
This is a slightly updated version of Pir Shukarullah Shah accepted answer to show a more functional approach over class approach.
// render a single section.data item
const itemRenderer = (item) => <Text>{item}</Text>
return (
<SectionList
sections={listData}
renderItem={(section, index) => {
if (index % numCols) { // items are already consumed
return null
}
// grab all items for the row
const rowItems = section.data.slice(index, index+numCols)
// wrap selected items in a "row" View
return <View
style={{
flexDirection:"row",
justifiyContent:"space-between"
}}
>{rowItems.map(itemRenderer)}</View>
}}
/>)
Also if you have fixed width items you can calculate numCols dynamically here's an example for a full screen width SectionList:
const itemFixedWidth = 24
const listWidth = useWindowDimensions().width
const numCols = Math.floor(listWidth / itemFixedWidth)
I'm a new user to this site, otherwise I'd just upvote Fong's answer above. Slick, that one.
Just to further clarify the last sentence he wrote.
I used Dimensions.get('window').width on the section header like so:
renderSectionHeader={({ section: { title } }) => (
<View
style={{
width: Dimensions.get('window').width,
}}
>
<Text>
{title}
</Text>
</View>
)}
Though that method does throw a console warning about using flexWrap...