I'm trying to build a react native screen that has a left-right carousel, in the each panel of the carousel there is a vertical flatlist with a list of items. At most there are 8-10 carousel panels and between 5-30 items in the flat vertical flatlist so possibly 300 at the very most items rendered but generally 100.
I am calling an API and checking the data on my server every 2-3 seconds and setting the state in my component with the new data. This currently works and data data in the child components gets updated as it should.
Each item in the flatlist is clickable which triggers a modal popup to be launched in the page. The problem i am having is the modal popup takes 4-5 seconds to appear and dismiss. Also when the modal finally begins to disappear the animation is jerky and the dark background layer seems to flash as it is removed.
I've tried first with the built in modal and have also used the react-native-modal package and both are the same.
I've tried to use InteractionManager.runAfterInteractions and also shouldComponentUpdate(nextProps, nextState) to try to block my api calls until the animations complete or to stop my isModalVisible state property from causing a re-render when i change it.
Code below, any help would be appreciated.
import {
Text,
Button,
StyleSheet,
View,
FlatList,
Dimensions,
Image,
Animated,
SafeAreaView,
TouchableHighlight,
InteractionManager,
} from 'react-native';
import React from 'react';
import Title from './Title';
import CarouselMeeting from './CarouselMeeting';
import Modal from 'react-native-modal';
import Carousel from 'react-native-snap-carousel';
class MeetingDisplay extends React.Component {
constructor(props) {
super(props);
this.state = {
raceList: [],
meetingList: [],
meetingId: props.navigation.state.params.meetingId,
currentIndex: 0,
raceCount: 0,
activeIndex: 0,
isModalVisible: false,
}
this.refreshScreen = this.refreshScreen.bind(this)
}
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
Promise.all([fetch('http://apicallurl?id' + this.state.meetingId), fetch('http://apicallurl?id' + this.state.meetingId)])
.then(([res1, res2]) => {
return Promise.all([res1.json(), res2.json()])
})
.then(([res1, res2]) => {
this.setState({
raceList: res1,
meetingList: res2.Meets,
})
});
this.interval = setInterval(() => this.updateRaceList(), 3000);
});
}
componentDidUpdate(prevProps, prevState) {
InteractionManager.runAfterInteractions(() => {
if (prevState.meetingId !== this.state.meetingId) {
Promise.all([fetch('http://apicallurl?id' + this.state.meetingId), fetch('http://apicallurl?id' + this.state.meetingId)])
.then(([res1, res2]) => {
return Promise.all([res1.json(), res2.json()])
})
.then(([res1, res2]) => {
this.setState({
raceList: res1,
meetingList: res2.Meets,
})
});
}
});
}
async updateRaceList() {
InteractionManager.runAfterInteractions(() => {
fetch('http://apicallurl' + this.state.meetingId)
.then((response) => response.json())
.then((responseJson) => {
this.setState({
raceList: responseJson,
}, function () {
});
})
.catch((error) => {
console.error(error);
});
});
}
toggleModal = () => {
InteractionManager.runAfterInteractions(() => {
this.setState({ isModalVisible: !this.state.isModalVisible });
});
};
shouldComponentUpdate(nextProps, nextState) {
if(this.state.isModalVisible !== nextState.isModalVisible){
this.setState({ isModalVisible: nextState.isModalVisible})
return false;
} else return true;
}
render() {
const peek = 20;
const gutter = peek / 4;
const cardWidth = Dimensions.get('window').width - gutter * 2 - peek * 2;
const contentOffset = (Dimensions.get('window').width - (cardWidth + (gutter * 2))) / 2;
return (
<>
<Title heading={this.state.raceList.VenueName} />
<SafeAreaView style={{ flex: 1, backgroundColor: 'rebeccapurple', paddingTop: 50, }}>
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'center', }}>
<Carousel
layout={"default"}
useScrollView
ref={ref => this.Carousel = ref}
data={this.state.raceList.RaceList}
sliderWidth={cardWidth}
itemWidth={cardWidth - gutter * 2 - peek * 2}
onSnapToItem={index => this.setState({ activeIndex: index })}
renderItem={({ item }) => (
<Animated.View style={{
flex: 1,
paddingTop: 20,
width: cardWidth,
margin: gutter,
backgroundColor: 'blue',
justifyContent: 'center',
alignItems: 'center',
}}>
<FlatList
horizontal={false}
showsVerticalScrollIndicator={true}
legacyImplementation={false}
data={item.runner_list}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }, index) =>
<TouchableHighlight style={{ flex: 1, flexDirection: 'row' }} onPress={this.toggleModal} >
<Image style={{ width: 50, height: 50 }} source={{ uri: item.imageurl }} />
</TouchableHighlight>}
>
</FlatList>
</Animated.View>
)}
/>
</View>
</SafeAreaView>
<Modal isVisible={this.state.isModalVisible}
backdropTransitionOutTiming={1}>
<View style={{ flex: 1 }}>
<Text>Hello!</Text>
<Button title="Hide modal" onPress={this.toggleModal} />
</View>
</Modal>
</>
);
}
}
const styles = StyleSheet.create({
centeredView: {
flex: 1,
justifyContent: "center",
alignItems: "center",
marginTop: 22
},
modalView: {
margin: 20,
backgroundColor: "white",
borderRadius: 20,
padding: 35,
alignItems: "center",
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5
},
openButton: {
backgroundColor: "#F194FF",
borderRadius: 20,
padding: 10,
elevation: 2
},
textStyle: {
color: "white",
fontWeight: "bold",
textAlign: "center"
},
modalText: {
marginBottom: 15,
textAlign: "center"
}
});
export default MeetingDisplay;
ATTEMPT 1
I had an idea that it might be my use of a third party carousel library called 'react-native-snap-carousel' so i attempted to replace this with a terrible looking scrollview and had all my flatlists/items render in it but this did not improve the popup time delay which was still 2-3 seconds.
ATTEMPT 2
I found something called a react.purecomponent that potentially should carry out a shallow compare of state/props and only trigger a re-render when items/state has actually changed which might mean animations/ui thread whatever is causing the problem to stop. But no better (both on an emulator and on a device) still long pause before modal displays
class MeetingDisplay extends React.PureComponent
ATTEMPT 4
Take the flatlist out the equation by placing a button to trigger the modal outside the flatlist a the bottom of the page below the bottom carousel.
....</View>
</SafeAreaView>
<Modal
visible={this.state.isModalVisible}
backdropTransitionOutTiming={1}
>
<View style={{ flex: 1 }}>
<Text>Hello!</Text>
<Button title="Hide modal" onPress={this.toggleModal} />
</View>
</Modal>
<Button title="Show modal" onPress={this.toggleModal} />
</>
);....
This resulted in no improvement or performance. So what else is causing the issue. Is it the constant re-rendering of my components caused by the interval? So there must be a way of pausing the component re-rendering that i missing. Anyone?