Since updating react-dom and react-router-dom packages: behaviors executing additional times

浪子不回头ぞ 提交于 2020-01-06 04:46:06

问题


I've inherited a react/node/prismic application where we need to update the prismic-reactjs package, which resulted in needing to update a few others- in total, we changed:

prismic-reactjs: 0.2.0 → 1.1.0

react: 15.6.1 → 16.0.0

webpack: 3.12.0 → 4.39.2

react-dom: 15.6.1 → 16.0.0

react-router-dom: 4.1.2 → 5.0.1

extract-text-webpack-plugin (deprecated) → mini-css-extract-plugin

Then removed one use of "withRouter()" because of new error when starting up the local server (but I confirmed in another branch that making that edit alone doesn't produce the symptom below)

Context: we have a personMap that shows a icons for a set of people, and you click one of them to open a PersonMapStory modal, which shows that person's story, then links to everybody else in a list at the bottom. If you scroll down and click one of those links, the story up top is replaced accordingly (with the new person's story), and list below is everyone else. In the code, the button onClick behavior for these bottom links is setActivePerson() to this new person.

New bug: in this new branch, when I have the story modal open and simply hover off the page and back again, we're repeating the setQueuedUpPerson and setNoQueuedUpPerson calls that are associated with onMouseEnter and onMouseLeave. (Doesn't happen in the previous/production code). It's causing downstream problems and I'm trying to figure out how to prevent this.

Further info: I've already seen a propagation issue where the solution was to pass the event and call stopPropagation in the appropriate method, and this seems like the same kind of behavior. Is there a way to do the same sort of thing to have it stop listening to the hover behavior when the modal is up?

Updated PersonMap.js (from first suggested answer):

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

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

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

    setNoQueuedUpPerson(e) {
        this.setState({
            queuedUpPerson: null
        })
        e.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(e)
    }

    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>
                {
                    !this.state.isDesktop &&
                    <div className="person-map-graphic" ref={this.setTopRef}>
                        {
                            this.props.personStories.map(person => (
                                <PersonMapLocator
                                    x={person.x}
                                    y={person.y}
                                    key={person.name}
                                >
                                    <PersonMapPoint name={person.name}/>
                                </PersonMapLocator>
                            ))
                        }
                        <PersonMapBackground isVisible={this.state.hasBeenScrolledTo} />
                    </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 ? (e) => this.setQueuedUpPerson(e, person.name) : null}
                                        onMouseLeave={this.state.isDesktop ? this.setNoQueuedUpPerson : null}
                                        onFocus={this.state.isDesktop ? (e) => this.setQueuedUpPerson(e, 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)}
                                    >
                                        {
                                            this.state.isDesktop ?
                                                <PersonMapLocator
                                                    x={person.x}
                                                    y={person.y}
                                                >
                                                    <PersonMapPoint
                                                        name={person.name}
                                                        isInteractive
                                                        isActive={thisPersonIsQueued}
                                                        imageUrl={person.photo_url}
                                                    />
                                                    <PersonMapCallout
                                                        name={person.name}
                                                        photo={person.photo_url}
                                                        isOpen={thisPersonIsQueued}
                                                        isDesktop
                                                    />
                                                </PersonMapLocator> :
                                                <PersonMapCallout
                                                    name={person.name}
                                                    photo={person.photo_url}
                                                />
                                        }
                                        <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

Original 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

回答1:


UPDATE (as below does not work):

Could you key off there being an activePerson in state as to whether the modal is open or not, which would mean you wouldn't want setQueuedUpPerson or setNoQueuedUpPerson to call? Either change them like this:

setQueuedUpPerson(e, personName) {
    if(!this.state.activePerson) {
      this.setState({
          queuedUpPerson: personName,
          lastQueuedPerson: personName
      })
    }

    e.stopPropagation()
}

setNoQueuedUpPerson(e) {
    if(!this.state.activePerson) {
      this.setState({
          queuedUpPerson: null
      })
    }
    e.stopPropagation()
}

Or make the onMouseEnter and onMouseLeave events null if there is an activePerson?


You should be able to change

onMouseEnter={this.state.isDesktop ? () => this.setQueuedUpPerson(person.name) : null}

to

onMouseEnter={this.state.isDesktop ? (e) => this.setQueuedUpPerson(e,person.name) : null}

And then access the event as with your previous question to stop propagation:

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

With onMouseLeave, you just need to add the event parameter to your function as it is already exposed since you are passing the function reference directly, as opposed to calling it from an anonymous function like with onMouseEnter:

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


来源:https://stackoverflow.com/questions/58565713/since-updating-react-dom-and-react-router-dom-packages-behaviors-executing-addi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!