Justin C.
Justin C.

Reputation: 109

React-bootstrap Modal component opens/closes all modals when I map through a list

New to programming so I'm sorry if I'm not wording this correctly. I'm using a .map to render and list every single item on an array. For each item, I want the modal to open/close only the specific modal corresponding to each item in the array. However, when I click on the button to open the modal, every single one opens and closes. I believe this is because the modals are all set to an on/off button together. How can I set it it (with the .map value.id or something) so that only the specific modal opens and closes?

class DrinkMenu extends Component {
    constructor(props, context) {
        super(props, context);
        this.state = {
            show: false
        };
        this.handleHide = this.handleHide.bind(this);
    }

    handleHide() {
        this.setState({ show: false });
    }

    async componentDidMount() {
        let res = await axios.get('/getAllDrinks')
        this.props.updateDrinkMenu(res.data)
    }

    async addToCart(drinkObj) {
        let res = await axios.post('/addToCart', drinkObj)
        console.log(`Added ${drinkObj.name} to order.`)
    }

    render() {
        let drinkList = this.props.drinkMenu.map((drink) => {
            return (
                <div key={drink.id}>
                    <h5>{drink.name}</h5>
                    <h6>${drink.price}</h6>

                    <span
                        onClick={() => this.setState({ show: true })}
                    >
                        <strong>More Info</strong>
                        <br />
                        <button onClick={() => this.addToCart(drink)}>Add to Order</button>

                    </span>

                    <Modal
                        show={this.state.show}
                        onHide={this.handleHide}
                        container={this}
                        aria-labelledby="contained-modal-title"
                    >
                        <Modal.Header closeButton>
                            <Modal.Title id="contained-modal-title">
                                {drink.name}
                            </Modal.Title>
                        </Modal.Header>
                        <Modal.Body>
                            <p>{drink.sub_category} | ABV {drink.abv}% | {drink.origin}</p>
                            <Col xs={6} md={4}>
                                <Image className="drink-logo" src={drink.logo} thumbnail />
                            </Col>
                            <p className="drink-description"><strong>Description</strong><br />{drink.description}</p>
                            <p href={drink.website}>Website</p>
                        </Modal.Body>
                        <Modal.Footer>
                            <Button onClick={this.handleHide}>Close</Button>
                        </Modal.Footer>
                    </Modal>
                </div>
            )
        })

        return (
            <div>
                <h2>Drink Menu</h2>
                <div>
                    {drinkList}
                </div>
            </div>
        )
    }
}

Upvotes: 3

Views: 1352

Answers (1)

Pranay Tripathi
Pranay Tripathi

Reputation: 1882

From the code you have shared, I see that you are handling all the Model with the same state value, i.e. show. This is causing all the state for all the Models to be true hence all of them as shown.

To solve this, you can extract your whole component in a new React class which has just the functionality to show Modal as per the independent state. So your new React component will look something like this:

class DrinkComponent extends React.Component {
    constructor(props) {
        super(props);
        this.handleHide = this.handleHide.bind(this);
        this.state = {
            show: false,
        }
    }
    handleHide() {
        this.setState({ show: false });
    }
    render() {
        const { drink } = this.props;
        return (<div key={drink.id}>
            <h5>{drink.name}</h5>
            <h6>${drink.price}</h6>
            <span
                onClick={() => this.setState({ show: true })}
            >
                <strong>More Info</strong>
                <br />
                <button onClick={() => this.props.addToCart(drink)}>Add to Order</button>
            </span>
            <Modal
                show={this.state.show}
                onHide={this.handleHide}
                container={this}
                aria-labelledby="contained-modal-title"
            >
                <Modal.Header closeButton>
                    <Modal.Title id="contained-modal-title">
                        {drink.name}
                    </Modal.Title>
                </Modal.Header>
                <Modal.Body>
                    <p>{drink.sub_category} | ABV {drink.abv}% | {drink.origin}</p>
                    <Col xs={6} md={4}>
                        <Image className="drink-logo" src={drink.logo} thumbnail />
                    </Col>
                    <p className="drink-description"><strong>Description</strong><br />{drink.description}</p>
                    <p href={drink.website}>Website</p>
                </Modal.Body>
                <Modal.Footer>
                    <Button onClick={this.handleHide}>Close</Button>
                </Modal.Footer>
            </Modal>
        </div>);
    }
}

In this case, each DrinkComponent will have its independent state of showing and hiding of the model. Now we have to just modify your existing render function in DrinkMenu, to display DrinkComponent. So your render function will look something like this:

render() {
        let drinkList = this.props.drinkMenu.map((drink) => (<DrinkComponent drink={drink} addToCart={this.addToCart}/>));
        return (
            <div>
                <h2>Drink Menu</h2>
                <div>
                    {drinkList}
                </div>
            </div>
        )
    }

Also you can remove the show state from DrinkMenu as it wont be needed there. Hope it helps.

Upvotes: 3

Related Questions