I first make an Ajax call (to an API) which provides me some data, a list of achievements (array of objects). I would like to loop through this array, show the first achievement as a Modal and on click of a button close the modal then show the next one (next achievement) and so on.
Ajax call providing the data:
getAchievements = () => {
fetch(url + '/achievements', {
method: 'get',
headers: {
Accept: 'application/json',
'Content-type': 'application/json'
}
})
.then((data) => data.json())
.then((data) => {
this.props.addData({
achievements: data.achievements
})
if(this.props.store.achievements.length > 0) {
this.setState({
showAchievementModal: true
})
}
})
.catch((error) => {
console.error(error)
})
}
Here I show the modals:
render() {
return (
{this.state.showAchievementModal &&
<Modal
animationType={'fade'}
visible={this.props.store.isModalAchievementVisible}
>
{this.props.store.achievements.map((data,index)=>{
return(
<View key={index}>
<View style={styles.container}>
<Text>{data.title}</Text>
<Text>{data.description}</Text>
<TouchableOpacity onPress={this.closeModal}>
<Text>Collect</Text>
</TouchableOpacity>
</View>
</View>
)
})}
</Modal>
}
)
}
At the moment all the Modals open at the same time. How could I open them one after the other after clicking the Collect button?
I first make an Ajax call (to an API) which provides me some data, a list of achievements (array of objects). I would like to loop through this array, show the first achievement as a Modal and on click of a button close the modal then show the next one (next achievement) and so on.
Ajax call providing the data:
getAchievements = () => {
fetch(url + '/achievements', {
method: 'get',
headers: {
Accept: 'application/json',
'Content-type': 'application/json'
}
})
.then((data) => data.json())
.then((data) => {
this.props.addData({
achievements: data.achievements
})
if(this.props.store.achievements.length > 0) {
this.setState({
showAchievementModal: true
})
}
})
.catch((error) => {
console.error(error)
})
}
Here I show the modals:
render() {
return (
{this.state.showAchievementModal &&
<Modal
animationType={'fade'}
visible={this.props.store.isModalAchievementVisible}
>
{this.props.store.achievements.map((data,index)=>{
return(
<View key={index}>
<View style={styles.container}>
<Text>{data.title}</Text>
<Text>{data.description}</Text>
<TouchableOpacity onPress={this.closeModal}>
<Text>Collect</Text>
</TouchableOpacity>
</View>
</View>
)
})}
</Modal>
}
)
}
At the moment all the Modals open at the same time. How could I open them one after the other after clicking the Collect button?
Share Improve this question asked Mar 9, 2019 at 14:35 JohnJohn 3,79914 gold badges44 silver badges49 bronze badges2 Answers
Reset to default 2This is the updated version of my code that works:
Initialising activeModalIndex in the constructor:
constructor(props) {
super(props)
this.state = {
activeModalIndex: 0
}
}
Get achievements:
getAchievements = () => {
if(this.props.store.achievements.length > 0) {
this.setState({
showAchievementModal: true,
activeModalIndex: 0,
})
}
}
Render function:
render() {
return this.props.store.achievements.map((data,index) => this.state.activeModalIndex === index &&
<Modal>
<View key={index}>
<View style={styles.container}>
<Text>{data.title}</Text>
<Text>{data.description}</Text>
<TouchableOpacity onPress={this.closeModal}>
<Text>Collect</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
)
}
Close Modal:
closeModal = () => {
const maxIndex = this.props.store.achievements.length - 1
const currentIndex = this.state.activeModalIndex
const isLastModal = currentIndex === maxIndex
const newIndex = isLastModal? -1: currentIndex +1
this.setState({
activeModalIndex: newIndex
})
}
The problem is that you have multiple Modals on your page and they all use the same boolean to check if they should be rendered. Initially, showAchievementModal is set to true, so all modals are rendered. Furthermore, after you set showAchievementModal to false in closeModal, it will permanently stay false, so no additional modals will get rendered.
render() {
return (
{this.state.showAchievementModal &&
<Modal
...
</Modal>
}
)
}
Instead of showAchievementModal you should be keeping track of index of active modal. So, after you fetch the list of achievements from your API, set the activeModalIndex to 0. After the user dismisses this first modal, set the activeModalIndex to 1 inside the closeModal method, then set it to 2 after the second modal is closed and so on.
Now, for every modal to correspond to a single achievement, we must map each element of the achievements array to a single Modal and conditionally render it only if its corresponding index is the active one.
render() {
const achievements = this.props.store.achievements;
const { activeModalIndex } = this.state;
return achievements.map((data, index) => activeModalIndex === index &&
<Modal key={index}>
<View>
<View style={styles.container}>
<Text>{data.title}</Text>
<Text>{data.description}</Text>
<TouchableOpacity onPress={this.closeModal}>
<Text>Collect</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
)
}
When the users dismisses the currently active modal, simply increment the index of the active modal and the next modal will appear instead of the current one. If the new incremented value is equal or larger than the array length, nothing will get rendered, so no need to check for max index value before setting new state.
closeModal = () => {
this.setState(previousState => ({
activeModalIndex: previousState.activeModalIndex + 1,
}))
}
Also, please read about the dangers of setting index as key when rendering lists. If you happen to need ordering achievements by some value/priority and users can retrieve multiple pages of their achievements, it might cause rendering wrong ponents.