Search code examples
javascriptreactjsreact-nativefor-loopreact-modal

React native - open multiple modals one after the other inside for loop


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?


Solution

  • 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 components.