React-bootstrap Модальный компонент открывает / закрывает все модальные окна, когда я отображаю список

Новичок в программировании, поэтому извините, если я это неправильно сформулировал. Я использую .map для рендеринга и перечисления каждого элемента в массиве. Для каждого элемента я хочу, чтобы модальное окно открывало / закрывало только конкретное модальное окно, соответствующее каждому элементу в массиве. Однако, когда я нажимаю кнопку, чтобы открыть модальное окно, все открывается и закрывается. Я считаю, что это потому, что все модальные окна настроены на кнопку включения / выключения вместе. Как я могу установить его (с .map value.id или что-то в этом роде), чтобы открывались и закрывались только определенные модальные окна?

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>
        )
    }
}

person Justin C.    schedule 22.11.2018    source источник


Ответы (1)


Из кода, которым вы поделились, я вижу, что вы обрабатываете все Model с одним и тем же значением состояния, то есть show. Это приводит к тому, что все состояния для всех Models становятся true, следовательно, все они, как показано.

Чтобы решить эту проблему, вы можете извлечь весь свой компонент в новый класс React, который имеет только функциональные возможности для отображения Modal в соответствии с независимым состоянием. Итак, ваш новый компонент React будет выглядеть примерно так:

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>);
    }
}

В этом случае каждый DrinkComponent будет иметь свое независимое состояние отображения и скрытия модели. Теперь нам нужно просто изменить вашу существующую render функцию в DrinkMenu, чтобы отобразить DrinkComponent. Итак, ваша render функция будет выглядеть примерно так:

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

Также вы можете удалить шоу state из DrinkMenu, поскольку оно там не понадобится. Надеюсь, поможет.

person Pranay Tripathi    schedule 22.11.2018
comment
Это исправило! Большое спасибо! Очень признателен за вашу помощь! - person Justin C.; 22.11.2018
comment
@JustinC. Замечание о вашем _1 _..., вероятно, он не должен быть асинхронным, но, поскольку axios возвращает обещания, они могут "тогда". componentDidMount () {axios.get ('/ getAllDrinks') .then (функция (res) {this.props.updateDrinkMenu (res.data);}); } Во-вторых, странно, что этот компонент вызывает обновление меню напитков, которое он также отображает из свойств. Обычно он вычисляется родительским компонентом, а затем передается, как и вы. - person Drew Reese; 22.11.2018
comment
@JustinC, пожалуйста, примите ответ, если проблема устранилась. - person Pranay Tripathi; 22.11.2018
comment
@DrewReese Привет, Дрю. Между использованием async / await или просто .then есть ли один метод, который лучше, чем другой, или считается лучшей практикой? Что касается updateDrinkMenus, я также использую Redux, и он обновляет состояние Redux, чтобы я мог отображать все данные и отображать их. Еще раз я не уверен, что делаю это наилучшим образом, поэтому, если у вас есть какие-либо советы, я буду признателен! - person Justin C.; 22.11.2018
comment
@JustinC. Функционально async / await и обещания - это две разные вещи, но обычно они работают вместе. Ключевой аспект, на который следует обратить внимание, заключается в том, что поток javascript будет приостанавливаться на await, пока эта строка не завершит выполнение, прежде чем продолжить в этой функции. Однако с обещанием разрешение / отклонение помещается в очередь для асинхронного выполнения позже, поэтому остальная часть функции выполняется. В этом случае с response вы не хотите без нужды приостанавливать его функции жизненного цикла. - person Drew Reese; 22.11.2018
comment
Обновление состояния redux должно происходить при загрузке приложения или когда родительский контейнер готовится отобразить список. Родитель должен отвечать за получение обновленных вариантов напитков, а затем передавать их в список с помощью функции mapStateToProps redux. - person Drew Reese; 22.11.2018
comment
@PranayTripathi Мой список напитков в настоящее время отображает весь список напитков, которые есть в моей базе данных. Если бы я хотел разбить его на подкатегории, как я мог бы это сделать? Сейчас у меня есть что-то вроде: render () {let lagers = this.props.drinkMenu.filter (drink = ›drink.category === 'lagers'). Map ((drink) =› (‹DrinkComponent drink = {drink}) addToCart = {this.addToCart} / ›)); возврат (‹div› ‹h2› лагеры ‹/h2› ‹div› {лагеры} ‹/div› ‹/div›)} - person Justin C.; 29.11.2018
comment
@PranayTripathi Тем не менее, у меня есть около 12 различных подкатегорий, поэтому разбиение на них, как это, в конечном итоге будет немного повторяющимся, а не СУХИМ. Есть ли у вас какие-либо рекомендации, как лучше подойти к этому? Для справки я пытаюсь создать что-то вроде этого: ardhouse.com/menu-listing/beer - person Justin C.; 29.11.2018
comment
ну, я не вижу, чтобы логика изменилась, пока вы не измените структуру данных. - person Pranay Tripathi; 29.11.2018