问题
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