После обновления пакетов response-dom и response-router-dom: чрезмерное поведение li onClick

Я унаследовал приложение response / node / prismic, в котором нам нужно обновить пакет prismic-reactjs, что привело к необходимости обновить несколько других - в итоге мы изменили:

prismic-reactjs: 0.2.0 → 1.1.0

реакция: 15.6.1 → 16.0.0

webpack: 3.12.0 → 4.39.2

реакция-дом: 15.6.1 → 16.0.0

реагировать-маршрутизатор-дом: 4.1.2 → 5.0.1

extract-text-webpack-plugin (устарел) → mini-css-extract-plugin

Затем было удалено одно использование withRouter () из-за новой ошибки при запуске локального сервера (но я подтвердил в другой ветке, что внесение этого изменения само по себе не вызывает симптома ниже)

Контекст: у нас есть personMap, на котором отображаются значки для группы людей, и вы щелкаете по одному из них, чтобы открыть модальное окно PersonMapStory, которое показывает историю этого человека, а затем ссылается на всех остальных в списке по адресу дно. Если вы прокрутите вниз и щелкните одну из этих ссылок, история вверху будет заменена соответствующим образом (историей нового человека), а в списке ниже будут представлены все остальные. В коде поведение кнопки onClick для этих нижних ссылок установлено ActivePerson () для этого нового человека.

Новая ошибка: в этой новой ветке, когда мы нажимаем нижнюю ссылку, она вызывает setActivePerson () для человека №2, но затем снова вызывает setActivePerson () для человека №1! (Вернемся к исходному поведению PersonMap li onClick). Таким образом, симптом заключается в том, что вы видите первую историю, прокручиваете вниз и щелкаете ссылку другого человека, но модальное окно вообще не обновляется (хотя на самом деле оно обновляется дважды). В текущей / производственной / рабочей ветке он просто обновляется один раз для нового человека и все. Это новое поведение действительно похоже на эту проблему распространения onClick. Я попытался добавить stopPropagation () в соответствующие методы, но безуспешно.

Дополнительная информация: в этой ветке похоже, что ЛЮБОЙ щелчок изнутри этого модального окна (даже если я нажимаю на историю, отображаемую вверху) вызывает SetActivePerson () с исходным человеком из элемента строки PersonMap onClick, когда thisPersonIsQueued истинно. (Итак, если я прокручиваю вниз и нажимаю нового человека, мы будем SetActivePerson person # 2, а сразу за ним SetActivePerson person # 1, так что модальное окно сохраняет ту же историю и никогда не обновляется). В состоянии PersonMap (просматривается в инструментах / компонентах разработчика Chrome) есть queuedUpPatient: обычно в этом сценарии (в исходном / производственном коде) queuedUpPatient имеет значение null, но в этой новой ветке queuedUpPatient сохраняет значение Person # 1. (или он очищен и переустановлен).

Возможная подсказка в отладчике: после того, как мы вызовем SetActivePerson для №2 (что является последним шагом, который происходит в нашей обычной / производственной ветке), прежде чем мы увидим вызов setActivePerson для человека №1 из его поведения onClick , отладчик проводит меня через несколько методов в response-dom.development.js, начиная с самой последней строки callCallback (), внутри кода для следующего случая (см. ниже):

// Check that the browser supports the APIs we need to implement our special
// DEV version of invokeGuardedCallback
if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function' && typeof document 

Другие идеи о том, что может происходить, и как я могу заставить это снова нормально работать, чтобы мы могли продолжить обновление пакетов? Вот два соответствующих файла для карты и модального (ниже).

PersonMap.js:

import Modal from 'react-modal'
import PropTypes from 'prop-types'
import React from 'react'

import PersonMapPoint from './PersonMapPoint'
import PersonMapStory from './PersonMapStory'
import PersonMapCallout from './PersonMapCallout'
import PersonMapLocator from './PersonMapLocator'
import PersonMapBackground from './PersonMapBackground'

const CUSTOM_STYLES = {
    content: {
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        backgroundColor: '#fff',
        zIndex: 10,
        border: 'none'
    }
}

class PersonMap extends React.Component {
    constructor(props) {
        super(props)

        this.setActivePerson = this.setActivePerson.bind(this)
        this.setNoActivePerson = this.setNoActivePerson.bind(this)
        this.setQueuedUpPerson = this.setQueuedUpPerson.bind(this)
        this.setNoQueuedUpPerson = this.setNoQueuedUpPerson.bind(this)
        this.checkBlurEvent = this.checkBlurEvent.bind(this)
        this.setIsDesktop = this.setIsDesktop.bind(this)
        this.checkHasBeenScrolledTo = this.checkHasBeenScrolledTo.bind(this)
        this.setTopRef = this.setTopRef.bind(this)
        this.handleKeyPress = this.handleKeyPress.bind(this)

        this.state = {
            activePerson: null,
            queuedUpPerson: null,
            scrollPos: 0,
            isDesktop: false,
            hasBeenScrolledTo: false,
            lastQueuedPerson: null
        }
    }

    componentDidMount() {
        this.setIsDesktop()
        this.checkHasBeenScrolledTo()
        window.addEventListener('resize', this.setIsDesktop)
        window.addEventListener('scroll', this.checkHasBeenScrolledTo)
    }

    componentWillUnmount() {
        window.removeEventListener('resize', this.setIsDesktop)
        window.removeEventListener('scroll', this.checkHasBeenScrolledTo)
    }

    setTopRef(element) {
        this.topRef = element
    }

    setActivePerson(personName) {
        this.setState({
            activePerson: personName,
            scrollPos: window.scrollY
        })
        event.stopPropagation()
    }

    setNoActivePerson() {
        this.setState({
            queuedUpPerson: this.state.activePerson,
            activePerson: null
        }, () => {
            setTimeout(() => {
                window.scrollTo(0, this.state.scrollPos)
            }, 50)
        })
    }

    setQueuedUpPerson(personName) {
        this.setState({
            queuedUpPerson: personName,
            lastQueuedPerson: personName
        })
    }

    setNoQueuedUpPerson() {
        this.setState({
            queuedUpPerson: null
        })
        event.stopPropagation()
    }

    handleKeyPress(e, name) {
        if (e.key !== ' ' && e.key !== 'Enter') {
            return
        }
        this.setActivePerson(name)
    }

    checkBlurEvent(e) {
        if (Array.from(e.currentTarget.childNodes[0].childNodes).includes(e.relatedTarget)) {
            return
        }
        this.setNoQueuedUpPerson()
    }

    render() {
        return (
            <section className="slice-area person-map CONSTRAIN">
                <div className="person-map-headers">
                    <div className="person-map-headers-inner">
                        <h1 className="person-map-title">
                            {this.props.title}
                        </h1>
                        <p className="person-map-disclaimer">
                            {this.props.disclaimer}
                        </p>
                    </div>
                </div>
                <div
                    className={`person-map-list-wrap${ this.state.isDesktop ? ' --desktop' : '' }`}
                    ref={this.state.isDesktop && this.setTopRef}
                >
                    { this.state.isDesktop &&
                        <PersonMapBackground isVisible={this.state.hasBeenScrolledTo}/>
                    }
                    <ul className="person-map-list">
                        {
                            this.props.personStories.map((person) => {
                                const thisPersonIsQueued = this.state.queuedUpPerson === person.name
                                const thisPersonIsActive = this.state.activePerson === person.name
                                const thisPersonWasLastQueued = this.state.lastQueuedPerson === person.name

                                return (
                                    <li
                                        key={person.name} className={`person-map-list-item${thisPersonWasLastQueued ? ' --active' : ''}`}
                                        onMouseEnter={this.state.isDesktop ? () => this.setQueuedUpPerson(person.name) : null}
                                        onMouseLeave={this.state.isDesktop ? this.setNoQueuedUpPerson : null}
                                        onFocus={this.state.isDesktop ? () => this.setQueuedUpPerson(person.name) : null}
                                        onBlur={this.state.isDesktop ? this.checkBlurEvent : null}
                                        onClick={thisPersonIsQueued || !this.state.isDesktop ? () => this.setActivePerson(person.name) : null}
                                        onKeyPress={(e) => this.handleKeyPress(e, person.name)}
                                    >
                                        {
                                                <PersonMapLocator
                                                    x={person.x}
                                                    y={person.y}
                                                >
                                        }
                                        <Modal
                                            isOpen={thisPersonIsActive}
                                            onRequestClose={this.setNoActivePerson}
                                            style={CUSTOM_STYLES}
                                        >
                                            <PersonMapStory
                                                name={person.name}
                                                photo={person.photo_url}
                                                story={person.story}
                                                setNoActivePerson={this.setNoActivePerson}
                                                setActivePerson={this.setActivePerson}
                                                isActive={thisPersonIsActive}
                                                otherPersons={this.props.personStories.filter((item) => item.name !== person.name).map((item) => ({ name: item.name, photo: item.photo_url }))}
                                                isDesktop={this.state.isDesktop}
                                            />
                                        </Modal>
                                    </li>
                                )
                            })
                        }
                    </ul>
                </div>
            </section>
        )
    }
}

PersonMap.propTypes = {
    title: PropTypes.string,
    disclaimer: PropTypes.string,
    personStories: PropTypes.arrayOf(PropTypes.shape({
        name: PropTypes.string,
        photo_url: PropTypes.string,
        x: PropTypes.number,
        y: PropTypes.number,
        story: PropTypes.shape({
            title: PropTypes.string,
            link: PropTypes.string,
            content: PropTypes.arrayOf(PropTypes.shape({
                question: PropTypes.string,
                answer: PropTypes.string
            }))
        })
    }))
}
export default PersonMap

PersonMapStory.js:

import PropTypes from 'prop-types'
import React from 'react'

import PersonMapPerson from './PersonMapPerson'


class PersonMapStory extends React.Component {
    constructor(props) {
        console.log("PersonMapStory constructor")
        super(props)

        this.deactivateThisPerson = this.deactivateThisPerson.bind(this)
        this.check = 0
    }

    deactivateThisPerson(backPressed = false) {
        if (!backPressed) {
            history.back()
        }
        console.log("Set no active person ")
        this.props.setNoActivePerson()
    }

    setActivePerson(name) {
        this.props.setActivePerson(name)
        event.stopPropagation()
    }


    componentDidMount() {
        const loc = window.location.pathname.substr(1)
        this.setState({ location: loc })
        const openState = { modalOpen: true }
        history.pushState(openState, 'signup-open', loc)
      }

    render() {
        return (
            <div className={`person-map-story${this.props.isActive ? ' --active' : ''}${this.props.isDesktop ? ' --desktop' : ''}`}>
                <h2 className="person-map-story-title">
                    { this.props.story.title }
                </h2>
                <button className="person-map-story-close-button" onClick={() => {this.deactivateThisPerson(false)}}>
                    <span className="person-map-story-close-button-text">
                        Close Story
                    </span>
                </button>
                <div className="person-map-story-body">
                    <span className="person-map-story-person-wrap">
                        <PersonMapPerson
                            photo={this.props.photo}
                            name={this.props.name}
                        />
                    </span>
                    {
                        this.props.story.content.map((section) => {
                            if (section) {
                                return (
                                    <div key={section.question}>
                                        <h3 className="person-map-story-question">{section.question}</h3>
                                        <p className="person-map-story-answer">
                                            {section.answer}
                                        </p>
                                    </div>
                                )
                            }
                        })
                    }
                    {
                        this.props.story.link &&
                        <a  href={this.props.story.link}
                            target="_blank"
                            className="person-map-story-more-link header-and-text-link"
                        >  Read More Stories
                        </a>
                    }

                    <ul className="person-map-story-other-list">
                        {
                            this.props.otherPersons.map((person) => (
                                <li
                                    key={person.name}
                                    className="person-map-story-other-list-item"
                                >
                                    <button className="person-map-story-other-button" onClick={() => this.setActivePerson(person.name)}>
                                        <PersonMapPerson
                                            name={person.name}
                                            photo={person.photo}
                                            ctaText="View their story"
                                        />
                                    </button>
                                </li>
                            ))
                        }
                    </ul>
                </div>
            </div>
        )
    }
}

PersonMapStory.propTypes = {
    name: PropTypes.string,
    photo: PropTypes.string,
    story: PropTypes.shape({
        title: PropTypes.string,
        link: PropTypes.string,
        content: PropTypes.arrayOf(PropTypes.shape({
            question: PropTypes.string,
            answer: PropTypes.string
        }))
    }),
    setNoActivePerson: PropTypes.func,
    setActivePerson: PropTypes.func,
    isActive: PropTypes.bool,
    otherPersons: PropTypes.arrayOf(PropTypes.shape({
        name: PropTypes.string,
        photo: PropTypes.string
    })),
    isDesktop: PropTypes.bool
}

export default PersonMapStory

person Diane Kaplan    schedule 23.10.2019    source источник
comment
У вас был event.stopPropagation() в PersonMapStory setActivePerson()? Определенно кажется, что щелчок распространяется, поскольку Modal является дочерним элементом <li>   -  person Anthony    schedule 23.10.2019
comment
спасибо Энтони! Да, это так странно - я пробовал добавить его в setActivePerson PersonMapStory и в setActivePerson и setNoQueuedUpPerson PersonMap, и он все еще продолжает этот второй вызов. Я обновил приведенный выше код на случай, если я что-то ошибся? Я новичок в интерфейсах ...   -  person Diane Kaplan    schedule 23.10.2019


Ответы (1)


Обновите свой onClick в PersonMapStory, чтобы получить доступ к объекту event:

<button className="person-map-story-other-button" onClick={e => this.setActivePerson(e, person.name)}>

и измените функцию setActivePerson(name) в PersonMapStory:

setActivePerson(event, name) {
    this.props.setActivePerson(name)
    event.stopPropagation()
}
person Anthony    schedule 23.10.2019
comment
Вот и все! Вот и все! Вот и все! Это работает!!! Я нашел соответствующий бит, но не выполнил его должным образом - большое вам спасибо !!! - person Diane Kaplan; 24.10.2019
comment
Быстрый последующий вопрос: какова наиболее вероятная причина того, почему это поведение изменилось вместе с этими обновлениями? Я не смог найти никаких связанных звуковых изменений в более высоких версиях response-dom и response-router-dom, и мне интересно, может ли это быть результатом того, что мне пришлось удалить withRouter ... - person Diane Kaplan; 25.10.2019
comment
Обновление: я подтвердил, что поведение будет таким же, если я ТОЛЬКО удаляю withRouter и не обновляю версии пакетов, поэтому это может быть взаимодействие между ними (или обновленный код в одном из пакетов), но не изменение кода сам по себе - person Diane Kaplan; 25.10.2019
comment
Что-то могло измениться с событиями реакции - вы действительно подняли основную версию - person Anthony; 25.10.2019
comment
Мальчик, дочитываешь, ты прав! Там много всего и совершенно новый код рендеринга. У меня есть еще один симптом, если у вас есть идеи и вы хотите дважды стать героем :) stackoverflow.com/questions/58565713/ - person Diane Kaplan; 26.10.2019